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), textview);
265 g_signal_connect(G_OBJECT(text), "motion-notify-event",
266 G_CALLBACK(textview_motion_notify), textview);
267 g_signal_connect(G_OBJECT(text), "leave-notify-event",
268 G_CALLBACK(textview_leave_notify), textview);
269 g_signal_connect(G_OBJECT(text), "visibility-notify-event",
270 G_CALLBACK(textview_visibility_notify), textview);
272 gtk_widget_show(scrolledwin);
274 vbox = gtk_vbox_new(FALSE, 0);
275 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
277 gtk_widget_show(vbox);
279 n_entries = sizeof(textview_link_popup_entries) /
280 sizeof(textview_link_popup_entries[0]);
281 link_popupmenu = menu_create_items(textview_link_popup_entries, n_entries,
282 "<UriPopupMenu>", &link_popupfactory,
285 n_entries = sizeof(textview_mail_popup_entries) /
286 sizeof(textview_mail_popup_entries[0]);
287 mail_popupmenu = menu_create_items(textview_mail_popup_entries, n_entries,
288 "<UriPopupMenu>", &mail_popupfactory,
291 textview->vbox = vbox;
292 textview->scrolledwin = scrolledwin;
293 textview->text = text;
294 textview->uri_list = NULL;
295 textview->body_pos = 0;
296 textview->show_all_headers = FALSE;
297 textview->last_buttonpress = GDK_NOTHING;
298 textview->link_popup_menu = link_popupmenu;
299 textview->link_popup_factory = link_popupfactory;
300 textview->mail_popup_menu = mail_popupmenu;
301 textview->mail_popup_factory = mail_popupfactory;
306 static void textview_create_tags(GtkTextView *text, TextView *textview)
308 GtkTextBuffer *buffer;
310 static PangoFontDescription *font_desc, *bold_font_desc;
313 font_desc = pango_font_description_from_string
316 if (!bold_font_desc) {
317 bold_font_desc = pango_font_description_from_string
319 pango_font_description_set_weight
320 (bold_font_desc, PANGO_WEIGHT_BOLD);
323 buffer = gtk_text_view_get_buffer(text);
325 gtk_text_buffer_create_tag(buffer, "header",
326 "pixels-above-lines", 0,
327 "pixels-above-lines-set", TRUE,
328 "pixels-below-lines", 0,
329 "pixels-below-lines-set", TRUE,
330 "font-desc", font_desc,
332 "left-margin-set", TRUE,
334 gtk_text_buffer_create_tag(buffer, "header_title",
335 "font-desc", bold_font_desc,
337 gtk_text_buffer_create_tag(buffer, "quote0",
338 "foreground-gdk", "e_colors[0],
340 gtk_text_buffer_create_tag(buffer, "quote1",
341 "foreground-gdk", "e_colors[1],
343 gtk_text_buffer_create_tag(buffer, "quote2",
344 "foreground-gdk", "e_colors[2],
346 gtk_text_buffer_create_tag(buffer, "emphasis",
347 "foreground-gdk", &emphasis_color,
349 gtk_text_buffer_create_tag(buffer, "signature",
350 "foreground-gdk", &signature_color,
352 tag = gtk_text_buffer_create_tag(buffer, "link",
353 "foreground-gdk", &uri_color,
355 gtk_text_buffer_create_tag(buffer, "link-hover",
356 "foreground-gdk", &uri_color,
357 "underline", PANGO_UNDERLINE_SINGLE,
360 g_signal_connect(G_OBJECT(tag), "event",
361 G_CALLBACK(textview_uri_button_pressed), textview);
364 void textview_init(TextView *textview)
367 hand_cursor = gdk_cursor_new(GDK_HAND2);
369 text_cursor = gdk_cursor_new(GDK_XTERM);
371 textview_update_message_colors();
372 textview_set_all_headers(textview, FALSE);
373 textview_set_font(textview, NULL);
374 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
377 void textview_update_message_colors(void)
379 GdkColor black = {0, 0, 0, 0};
381 if (prefs_common.enable_color) {
382 /* grab the quote colors, converting from an int to a GdkColor */
383 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
385 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
387 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
389 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
391 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
394 quote_colors[0] = quote_colors[1] = quote_colors[2] =
395 uri_color = emphasis_color = signature_color = black;
399 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
404 if ((fp = fopen(file, "rb")) == NULL) {
405 FILE_OP_ERROR(file, "fopen");
409 textview_clear(textview);
411 textview_add_parts(textview, mimeinfo);
415 textview_set_position(textview, 0);
418 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
422 g_return_if_fail(mimeinfo != NULL);
423 g_return_if_fail(fp != NULL);
425 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
426 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
427 textview_clear(textview);
428 textview_add_parts(textview, mimeinfo);
432 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
435 textview_clear(textview);
437 if (mimeinfo->type == MIMETYPE_MULTIPART)
438 textview_add_parts(textview, mimeinfo);
440 textview_write_body(textview, mimeinfo);
444 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
447 GtkTextBuffer *buffer;
450 GPtrArray *headers = NULL;
455 g_return_if_fail(mimeinfo != NULL);
456 text = GTK_TEXT_VIEW(textview->text);
457 buffer = gtk_text_view_get_buffer(text);
458 charcount = gtk_text_buffer_get_char_count(buffer);
459 gtk_text_buffer_get_end_iter(buffer, &iter);
461 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
463 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
466 fp = fopen(mimeinfo->data.filename, "rb");
467 fseek(fp, mimeinfo->offset, SEEK_SET);
468 headers = textview_scan_header(textview, fp);
471 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
472 textview_show_header(textview, headers);
473 procheader_header_array_destroy(headers);
479 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
480 content_type = procmime_get_content_type_str(mimeinfo->type,
483 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
485 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
486 name, content_type, mimeinfo->length);
488 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
489 content_type, mimeinfo->length);
491 g_free(content_type);
493 if (mimeinfo->type != MIMETYPE_TEXT) {
494 gtk_text_buffer_insert(buffer, &iter, buf, -1);
495 if (mimeinfo->type == MIMETYPE_IMAGE &&
496 prefs_common.inline_img ) {
498 GError *error = NULL;
504 fp = fopen(mimeinfo->data.filename, "rb");
505 fseek(fp, mimeinfo->offset, SEEK_SET);
507 filename = procmime_get_tmp_file_name(mimeinfo);
508 if (procmime_get_part(filename, mimeinfo) < 0) {
509 g_warning("Can't get the image file.");
514 pixbuf = gdk_pixbuf_new_from_file(filename, &error);
516 g_warning("%s\n", error->message);
520 g_warning("Can't load the image.");
525 /* if (prefs_common.resize_img) {
528 scaled = imageview_get_resized_pixbuf
529 (pixbuf, textview->text, 8);
530 g_object_unref(pixbuf);
534 /* uri_str = g_filename_to_uri(filename, NULL, NULL);
536 uri = g_new(RemoteURI, 1);
539 procmime_get_part_file_name(mimeinfo);
540 uri->start = gtk_text_iter_get_offset(&iter);
541 uri->end = uri->start + 1;
543 g_slist_append(textview->uri_list, uri);
545 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf);
546 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
548 g_object_unref(pixbuf);
551 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
552 if (prefs_common.display_header && (charcount > 0))
553 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
555 textview_write_body(textview, mimeinfo);
559 static void recursive_add_parts(TextView *textview, GNode *node)
564 mimeinfo = (MimeInfo *) node->data;
566 textview_add_part(textview, mimeinfo);
568 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
569 (mimeinfo->type != MIMETYPE_MESSAGE))
572 if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
573 GNode * prefered_body;
581 prefered_body = NULL;
584 for (iter = g_node_first_child(node) ; iter != NULL ;
585 iter = g_node_next_sibling(iter)) {
590 submime = (MimeInfo *) iter->data;
591 if (submime->type == MIMETYPE_TEXT)
594 if (submime->subtype != NULL) {
595 if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
599 if (score > prefered_score) {
600 prefered_score = score;
601 prefered_body = iter;
605 if (prefered_body != NULL) {
606 recursive_add_parts(textview, prefered_body);
610 for (iter = g_node_first_child(node) ; iter != NULL ;
611 iter = g_node_next_sibling(iter)) {
612 recursive_add_parts(textview, iter);
617 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
619 g_return_if_fail(mimeinfo != NULL);
621 recursive_add_parts(textview, mimeinfo->node);
624 #define TEXT_INSERT(str) \
625 gtk_text_buffer_insert(buffer, &iter, str, -1)
627 void textview_show_error(TextView *textview)
630 GtkTextBuffer *buffer;
633 textview_set_font(textview, NULL);
634 textview_clear(textview);
636 text = GTK_TEXT_VIEW(textview->text);
637 buffer = gtk_text_view_get_buffer(text);
638 gtk_text_buffer_get_start_iter(buffer, &iter);
640 TEXT_INSERT(_("This message can't be displayed.\n"));
644 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
647 GtkTextBuffer *buffer;
650 if (!partinfo) return;
652 textview_set_font(textview, NULL);
653 textview_clear(textview);
655 text = GTK_TEXT_VIEW(textview->text);
656 buffer = gtk_text_view_get_buffer(text);
657 gtk_text_buffer_get_start_iter(buffer, &iter);
659 TEXT_INSERT(_("The following can be performed on this part by "));
660 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
662 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
663 TEXT_INSERT(_(" To display as text select 'Display as text' "));
664 TEXT_INSERT(_("(Shortcut key: 't')\n"));
665 TEXT_INSERT(_(" To open with an external program select 'Open' "));
666 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
667 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
668 TEXT_INSERT(_("mouse button),\n"));
669 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
675 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
680 const gchar *charset;
682 if (textview->messageview->forced_charset)
683 charset = textview->messageview->forced_charset;
685 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
687 textview_set_font(textview, charset);
689 conv = conv_code_converter_new(charset);
691 procmime_force_encoding(textview->messageview->forced_encoding);
693 textview->is_in_signature = FALSE;
695 procmime_decode_content(mimeinfo);
697 if (!g_ascii_strcasecmp(mimeinfo->subtype, "html") &&
698 prefs_common.render_html) {
701 filename = procmime_get_tmp_file_name(mimeinfo);
702 if (procmime_get_part(filename, mimeinfo) == 0) {
703 tmpfp = fopen(filename, "rb");
704 textview_show_html(textview, tmpfp, conv);
709 } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
712 filename = procmime_get_tmp_file_name(mimeinfo);
713 if (procmime_get_part(filename, mimeinfo) == 0) {
714 tmpfp = fopen(filename, "rb");
715 textview_show_ertf(textview, tmpfp, conv);
721 tmpfp = fopen(mimeinfo->data.filename, "rb");
722 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
723 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
724 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
725 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
726 textview_write_line(textview, buf, conv);
730 conv_code_converter_destroy(conv);
731 procmime_force_encoding(0);
734 static void textview_show_html(TextView *textview, FILE *fp,
740 parser = html_parser_new(fp, conv);
741 g_return_if_fail(parser != NULL);
743 while ((str = html_parse(parser)) != NULL) {
744 if (parser->state == HTML_HREF) {
745 /* first time : get and copy the URL */
746 if (parser->href == NULL) {
747 /* ALF - the sylpheed html parser returns an empty string,
748 * if still inside an <a>, but already parsed past HREF */
749 str = strtok(str, " ");
751 parser->href = g_strdup(str);
752 /* the URL may (or not) be followed by the
754 str = strtok(NULL, "");
758 textview_write_link(textview, str, parser->href, NULL);
760 textview_write_line(textview, str, NULL);
762 textview_write_line(textview, "\n", NULL);
763 html_parser_destroy(parser);
766 static void textview_show_ertf(TextView *textview, FILE *fp,
772 parser = ertf_parser_new(fp, conv);
773 g_return_if_fail(parser != NULL);
775 while ((str = ertf_parse(parser)) != NULL) {
776 textview_write_line(textview, str, NULL);
779 ertf_parser_destroy(parser);
782 /* get_uri_part() - retrieves a URI starting from scanpos.
783 Returns TRUE if succesful */
784 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
785 const gchar **bp, const gchar **ep)
789 g_return_val_if_fail(start != NULL, FALSE);
790 g_return_val_if_fail(scanpos != NULL, FALSE);
791 g_return_val_if_fail(bp != NULL, FALSE);
792 g_return_val_if_fail(ep != NULL, FALSE);
796 /* find end point of URI */
797 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
798 if (!isgraph(*(const guchar *)ep_) ||
799 !IS_ASCII(*(const guchar *)ep_) ||
800 strchr("()<>\"", *ep_))
804 /* no punctuation at end of string */
806 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
807 * should pass some URI type to this function and decide on that whether
808 * to perform punctuation stripping */
810 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
812 for (; ep_ - 1 > scanpos + 1 &&
813 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
824 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
826 return g_strndup(bp, ep - bp);
829 /* valid mail address characters */
830 #define IS_RFC822_CHAR(ch) \
835 !strchr("(),;<>\"", (ch)))
837 /* alphabet and number within 7bit ASCII */
838 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && isalnum(ch))
839 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
841 static GHashTable *create_domain_tab(void)
843 static const gchar *toplvl_domains [] = {
845 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
846 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
847 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
848 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
849 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
850 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
851 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
852 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
853 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
854 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
855 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
856 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
857 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
858 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
859 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
860 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
861 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
862 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
863 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
864 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
865 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
866 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
867 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
868 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
869 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
870 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
873 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
875 g_return_val_if_fail(htab, NULL);
876 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
877 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
881 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
883 const gint MAX_LVL_DOM_NAME_LEN = 6;
884 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
885 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
888 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
891 for (p = buf; p < m && first < last; *p++ = *first++)
895 return g_hash_table_lookup(tab, buf) != NULL;
898 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
899 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
900 const gchar **bp, const gchar **ep)
902 /* more complex than the uri part because we need to scan back and forward starting from
903 * the scan position. */
904 gboolean result = FALSE;
905 const gchar *bp_ = NULL;
906 const gchar *ep_ = NULL;
907 static GHashTable *dom_tab;
908 const gchar *last_dot = NULL;
909 const gchar *prelast_dot = NULL;
910 const gchar *last_tld_char = NULL;
912 /* the informative part of the email address (describing the name
913 * of the email address owner) may contain quoted parts. the
914 * closure stack stores the last encountered quotes. */
915 gchar closure_stack[128];
916 gchar *ptr = closure_stack;
918 g_return_val_if_fail(start != NULL, FALSE);
919 g_return_val_if_fail(scanpos != NULL, FALSE);
920 g_return_val_if_fail(bp != NULL, FALSE);
921 g_return_val_if_fail(ep != NULL, FALSE);
924 dom_tab = create_domain_tab();
925 g_return_val_if_fail(dom_tab, FALSE);
927 /* scan start of address */
928 for (bp_ = scanpos - 1;
929 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
932 /* TODO: should start with an alnum? */
934 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
937 if (bp_ != scanpos) {
938 /* scan end of address */
939 for (ep_ = scanpos + 1;
940 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
942 prelast_dot = last_dot;
944 if (*(last_dot + 1) == '.') {
945 if (prelast_dot == NULL)
947 last_dot = prelast_dot;
952 /* TODO: really should terminate with an alnum? */
953 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
958 if (last_dot == NULL)
961 last_dot = prelast_dot;
962 if (last_dot == NULL || (scanpos + 1 >= last_dot))
966 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
967 if (*last_tld_char == '?')
970 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
977 if (!result) return FALSE;
979 if (*ep_ && *(bp_ - 1) == '"' && *(ep_) == '"'
980 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
981 && IS_RFC822_CHAR(*(ep_ + 3))) {
982 /* this informative part with an @ in it is
983 * followed by the email address */
986 /* go to matching '>' (or next non-rfc822 char, like \n) */
987 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
990 /* include the bracket */
991 if (*ep_ == '>') ep_++;
993 /* include the leading quote */
1001 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
1002 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
1005 /* see if this is <bracketed>; in this case we also scan for the informative part. */
1006 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
1009 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
1010 #define IN_STACK() (ptr > closure_stack)
1011 /* has underrun check */
1012 #define POP_STACK() if(IN_STACK()) --ptr
1013 /* has overrun check */
1014 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
1015 /* has underrun check */
1016 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
1020 /* scan for the informative part. */
1021 for (bp_ -= 2; bp_ >= start; bp_--) {
1022 /* if closure on the stack keep scanning */
1023 if (PEEK_STACK() == *bp_) {
1027 if (*bp_ == '\'' || *bp_ == '"') {
1032 /* if nothing in the closure stack, do the special conditions
1033 * the following if..else expression simply checks whether
1034 * a token is acceptable. if not acceptable, the clause
1035 * should terminate the loop with a 'break' */
1036 if (!PEEK_STACK()) {
1038 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
1039 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
1040 /* hyphens are allowed, but only in
1042 } else if (!strchr(",;:=?./+<>!&", *bp_)) {
1043 /* but anything not being a punctiation
1046 break; /* anything else is rejected */
1059 /* scan forward (should start with an alnum) */
1060 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
1070 #undef IS_ASCII_ALNUM
1071 #undef IS_RFC822_CHAR
1073 static gchar *make_email_string(const gchar *bp, const gchar *ep)
1075 /* returns a mailto: URI; mailto: is also used to detect the
1076 * uri type later on in the button_pressed signal handler */
1080 tmp = g_strndup(bp, ep - bp);
1081 result = g_strconcat("mailto:", tmp, NULL);
1087 static gchar *make_http_string(const gchar *bp, const gchar *ep)
1089 /* returns an http: URI; */
1093 tmp = g_strndup(bp, ep - bp);
1094 result = g_strconcat("http://", tmp, NULL);
1100 #define ADD_TXT_POS(bp_, ep_, pti_) \
1101 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1102 last = last->next; \
1103 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1104 last->next = NULL; \
1106 g_warning("alloc error scanning URIs\n"); \
1107 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1113 /* textview_make_clickable_parts() - colorizes clickable parts */
1114 static void textview_make_clickable_parts(TextView *textview,
1115 const gchar *fg_tag,
1116 const gchar *uri_tag,
1117 const gchar *linebuf)
1119 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1120 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1123 /* parse table - in order of priority */
1125 const gchar *needle; /* token */
1127 /* token search function */
1128 gchar *(*search) (const gchar *haystack,
1129 const gchar *needle);
1130 /* part parsing function */
1131 gboolean (*parse) (const gchar *start,
1132 const gchar *scanpos,
1135 /* part to URI function */
1136 gchar *(*build_uri) (const gchar *bp,
1140 static struct table parser[] = {
1141 {"http://", strcasestr, get_uri_part, make_uri_string},
1142 {"https://", strcasestr, get_uri_part, make_uri_string},
1143 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1144 {"www.", strcasestr, get_uri_part, make_http_string},
1145 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1146 {"@", strcasestr, get_email_part, make_email_string}
1148 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1151 const gchar *walk, *bp, *ep;
1154 const gchar *bp, *ep; /* text position */
1155 gint pti; /* index in parse table */
1156 struct txtpos *next; /* next */
1157 } head = {NULL, NULL, 0, NULL}, *last = &head;
1159 gtk_text_buffer_get_end_iter(buffer, &iter);
1161 /* parse for clickable parts, and build a list of begin and end positions */
1162 for (walk = linebuf, n = 0;;) {
1163 gint last_index = PARSE_ELEMS;
1164 gchar *scanpos = NULL;
1166 /* FIXME: this looks phony. scanning for anything in the parse table */
1167 for (n = 0; n < PARSE_ELEMS; n++) {
1170 tmp = parser[n].search(walk, parser[n].needle);
1172 if (scanpos == NULL || tmp < scanpos) {
1180 /* check if URI can be parsed */
1181 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1182 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1183 ADD_TXT_POS(bp, ep, last_index);
1187 strlen(parser[last_index].needle);
1192 /* colorize this line */
1194 const gchar *normal_text = linebuf;
1197 for (last = head.next; last != NULL;
1198 normal_text = last->ep, last = last->next) {
1200 uri = g_new(RemoteURI, 1);
1201 if (last->bp - normal_text > 0)
1202 gtk_text_buffer_insert_with_tags_by_name
1205 last->bp - normal_text,
1207 uri->uri = parser[last->pti].build_uri(last->bp,
1209 uri->start = gtk_text_iter_get_offset(&iter);
1210 gtk_text_buffer_insert_with_tags_by_name
1211 (buffer, &iter, last->bp, last->ep - last->bp,
1212 uri_tag, fg_tag, NULL);
1213 uri->end = gtk_text_iter_get_offset(&iter);
1214 textview->uri_list =
1215 g_slist_append(textview->uri_list, uri);
1219 gtk_text_buffer_insert_with_tags_by_name
1220 (buffer, &iter, normal_text, -1, fg_tag, NULL);
1222 gtk_text_buffer_insert_with_tags_by_name
1223 (buffer, &iter, linebuf, -1, fg_tag, NULL);
1229 static void textview_write_line(TextView *textview, const gchar *str,
1230 CodeConverter *conv)
1233 GtkTextBuffer *buffer;
1235 gchar buf[BUFFSIZE];
1237 gint quotelevel = -1;
1238 gchar quote_tag_str[10];
1240 text = GTK_TEXT_VIEW(textview->text);
1241 buffer = gtk_text_view_get_buffer(text);
1242 gtk_text_buffer_get_end_iter(buffer, &iter);
1245 strncpy2(buf, str, sizeof(buf));
1246 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1247 conv_utf8todisp(buf, sizeof(buf), str);
1250 //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1253 /* change color of quotation
1254 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1255 Up to 3 levels of quotations are detected, and each
1256 level is colored using a different color. */
1257 if (prefs_common.enable_color
1258 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1259 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1261 /* set up the correct foreground color */
1262 if (quotelevel > 2) {
1263 /* recycle colors */
1264 if (prefs_common.recycle_quote_colors)
1271 if (quotelevel == -1)
1274 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1275 "quote%d", quotelevel);
1276 fg_color = quote_tag_str;
1279 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1280 fg_color = "signature";
1281 textview->is_in_signature = TRUE;
1284 if (prefs_common.enable_color)
1285 textview_make_clickable_parts(textview, fg_color, "link", buf);
1287 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1290 void textview_write_link(TextView *textview, const gchar *str,
1291 const gchar *uri, CodeConverter *conv)
1293 GdkColor *link_color = NULL;
1295 GtkTextBuffer *buffer;
1297 gchar buf[BUFFSIZE];
1304 text = GTK_TEXT_VIEW(textview->text);
1305 buffer = gtk_text_view_get_buffer(text);
1306 gtk_text_buffer_get_end_iter(buffer, &iter);
1309 strncpy2(buf, str, sizeof(buf));
1310 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1311 conv_utf8todisp(buf, sizeof(buf), str);
1315 gtk_text_buffer_get_end_iter(buffer, &iter);
1317 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1318 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1320 if (prefs_common.enable_color) {
1321 link_color = &uri_color;
1323 r_uri = g_new(RemoteURI, 1);
1324 r_uri->uri = g_strdup(uri);
1325 r_uri->start = gtk_text_iter_get_offset(&iter);
1326 gtk_text_buffer_insert_with_tags_by_name
1327 (buffer, &iter, bufp, -1, "link", NULL);
1328 r_uri->end = gtk_text_iter_get_offset(&iter);
1329 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1332 void textview_clear(TextView *textview)
1334 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1335 GtkTextBuffer *buffer;
1337 buffer = gtk_text_view_get_buffer(text);
1338 gtk_text_buffer_set_text(buffer, "", -1);
1340 TEXTVIEW_STATUSBAR_POP(textview);
1341 textview_uri_list_remove_all(textview->uri_list);
1342 textview->uri_list = NULL;
1344 textview->body_pos = 0;
1347 void textview_destroy(TextView *textview)
1349 textview_uri_list_remove_all(textview->uri_list);
1350 textview->uri_list = NULL;
1355 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1357 textview->show_all_headers = all_headers;
1360 void textview_set_font(TextView *textview, const gchar *codeset)
1362 if (prefs_common.textfont) {
1363 PangoFontDescription *font_desc = NULL;
1365 font_desc = pango_font_description_from_string
1366 (prefs_common.textfont);
1368 gtk_widget_modify_font(textview->text, font_desc);
1369 pango_font_description_free(font_desc);
1372 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1373 prefs_common.line_space / 2);
1374 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1375 prefs_common.line_space / 2);
1378 void textview_set_text(TextView *textview, const gchar *text)
1381 GtkTextBuffer *buffer;
1383 g_return_if_fail(textview != NULL);
1384 g_return_if_fail(text != NULL);
1386 textview_clear(textview);
1388 view = GTK_TEXT_VIEW(textview->text);
1389 buffer = gtk_text_view_get_buffer(view);
1390 gtk_text_buffer_set_text(buffer, text, strlen(text));
1406 H_ORGANIZATION = 11,
1409 void textview_set_position(TextView *textview, gint pos)
1411 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1412 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1415 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1416 gtk_text_buffer_place_cursor(buffer, &iter);
1417 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1420 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1422 gchar buf[BUFFSIZE];
1423 GPtrArray *headers, *sorted_headers;
1424 GSList *disphdr_list;
1428 g_return_val_if_fail(fp != NULL, NULL);
1430 if (textview->show_all_headers)
1431 return procheader_get_header_array_asis(fp);
1433 if (!prefs_common.display_header) {
1434 while (fgets(buf, sizeof(buf), fp) != NULL)
1435 if (buf[0] == '\r' || buf[0] == '\n') break;
1439 headers = procheader_get_header_array_asis(fp);
1441 sorted_headers = g_ptr_array_new();
1443 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1444 disphdr_list = disphdr_list->next) {
1445 DisplayHeaderProp *dp =
1446 (DisplayHeaderProp *)disphdr_list->data;
1448 for (i = 0; i < headers->len; i++) {
1449 header = g_ptr_array_index(headers, i);
1451 if (procheader_headername_equal(header->name,
1454 procheader_header_free(header);
1456 g_ptr_array_add(sorted_headers, header);
1458 g_ptr_array_remove_index(headers, i);
1464 if (prefs_common.show_other_header) {
1465 for (i = 0; i < headers->len; i++) {
1466 header = g_ptr_array_index(headers, i);
1467 g_ptr_array_add(sorted_headers, header);
1469 g_ptr_array_free(headers, TRUE);
1471 procheader_header_array_destroy(headers);
1474 return sorted_headers;
1477 static void textview_show_header(TextView *textview, GPtrArray *headers)
1479 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1480 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1485 g_return_if_fail(headers != NULL);
1487 for (i = 0; i < headers->len; i++) {
1488 header = g_ptr_array_index(headers, i);
1489 g_return_if_fail(header->name != NULL);
1491 gtk_text_buffer_get_end_iter (buffer, &iter);
1492 gtk_text_buffer_insert_with_tags_by_name
1493 (buffer, &iter, header->name, -1,
1494 "header_title", "header", NULL);
1495 if (header->name[strlen(header->name) - 1] != ' ')
1496 gtk_text_buffer_insert_with_tags_by_name
1497 (buffer, &iter, " ", 1,
1498 "header_title", "header", NULL);
1500 if (procheader_headername_equal(header->name, "Subject") ||
1501 procheader_headername_equal(header->name, "From") ||
1502 procheader_headername_equal(header->name, "To") ||
1503 procheader_headername_equal(header->name, "Cc"))
1504 unfold_line(header->body);
1506 if (prefs_common.enable_color &&
1507 (procheader_headername_equal(header->name, "X-Mailer") ||
1508 procheader_headername_equal(header->name,
1510 strstr(header->body, "Sylpheed") != NULL) {
1511 gtk_text_buffer_get_end_iter (buffer, &iter);
1512 gtk_text_buffer_insert_with_tags_by_name
1513 (buffer, &iter, header->body, -1,
1514 "header", "emphasis", NULL);
1515 } else if (prefs_common.enable_color) {
1516 textview_make_clickable_parts(textview, "header", "link",
1519 textview_make_clickable_parts(textview, "header", NULL,
1522 gtk_text_buffer_get_end_iter (buffer, &iter);
1523 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1528 gboolean textview_search_string(TextView *textview, const gchar *str,
1531 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1532 GtkTextBuffer *buffer;
1533 GtkTextIter iter, match_pos;
1537 g_return_val_if_fail(str != NULL, FALSE);
1539 buffer = gtk_text_view_get_buffer(text);
1541 len = g_utf8_strlen(str, -1);
1542 g_return_val_if_fail(len >= 0, FALSE);
1544 mark = gtk_text_buffer_get_insert(buffer);
1545 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1547 if (gtkut_text_buffer_find(buffer, &iter, str, case_sens,
1549 GtkTextIter end = match_pos;
1551 gtk_text_iter_forward_chars(&end, len);
1552 /* place "insert" at the last character */
1553 gtk_text_buffer_select_range(buffer, &end, &match_pos);
1554 gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1561 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1564 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1565 GtkTextBuffer *buffer;
1566 GtkTextIter iter, match_pos;
1570 g_return_val_if_fail(str != NULL, FALSE);
1572 buffer = gtk_text_view_get_buffer(text);
1574 len = g_utf8_strlen(str, -1);
1575 g_return_val_if_fail(len >= 0, FALSE);
1577 mark = gtk_text_buffer_get_insert(buffer);
1578 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1580 if (gtkut_text_buffer_find_backward(buffer, &iter, str, case_sens,
1582 GtkTextIter end = match_pos;
1584 gtk_text_iter_forward_chars(&end, len);
1585 gtk_text_buffer_select_range(buffer, &match_pos, &end);
1586 gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1593 void textview_scroll_one_line(TextView *textview, gboolean up)
1595 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1596 GtkAdjustment *vadj = text->vadjustment;
1599 if (prefs_common.enable_smooth_scroll) {
1600 textview_smooth_scroll_one_line(textview, up);
1605 upper = vadj->upper - vadj->page_size;
1606 if (vadj->value < upper) {
1607 vadj->value += vadj->step_increment;
1608 vadj->value = MIN(vadj->value, upper);
1609 g_signal_emit_by_name(G_OBJECT(vadj),
1610 "value_changed", 0);
1613 if (vadj->value > 0.0) {
1614 vadj->value -= vadj->step_increment;
1615 vadj->value = MAX(vadj->value, 0.0);
1616 g_signal_emit_by_name(G_OBJECT(vadj),
1617 "value_changed", 0);
1622 gboolean textview_scroll_page(TextView *textview, gboolean up)
1624 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1625 GtkAdjustment *vadj = text->vadjustment;
1629 if (prefs_common.enable_smooth_scroll)
1630 return textview_smooth_scroll_page(textview, up);
1632 if (prefs_common.scroll_halfpage)
1633 page_incr = vadj->page_increment / 2;
1635 page_incr = vadj->page_increment;
1638 upper = vadj->upper - vadj->page_size;
1639 if (vadj->value < upper) {
1640 vadj->value += page_incr;
1641 vadj->value = MIN(vadj->value, upper);
1642 g_signal_emit_by_name(G_OBJECT(vadj),
1643 "value_changed", 0);
1647 if (vadj->value > 0.0) {
1648 vadj->value -= page_incr;
1649 vadj->value = MAX(vadj->value, 0.0);
1650 g_signal_emit_by_name(G_OBJECT(vadj),
1651 "value_changed", 0);
1659 static void textview_smooth_scroll_do(TextView *textview,
1660 gfloat old_value, gfloat last_value,
1663 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1664 GtkAdjustment *vadj = text->vadjustment;
1669 if (old_value < last_value) {
1670 change_value = last_value - old_value;
1673 change_value = old_value - last_value;
1677 for (i = step; i <= change_value; i += step) {
1678 vadj->value = old_value + (up ? -i : i);
1679 g_signal_emit_by_name(G_OBJECT(vadj),
1680 "value_changed", 0);
1683 vadj->value = last_value;
1684 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1687 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1689 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1690 GtkAdjustment *vadj = text->vadjustment;
1696 upper = vadj->upper - vadj->page_size;
1697 if (vadj->value < upper) {
1698 old_value = vadj->value;
1699 last_value = vadj->value + vadj->step_increment;
1700 last_value = MIN(last_value, upper);
1702 textview_smooth_scroll_do(textview, old_value,
1704 prefs_common.scroll_step);
1707 if (vadj->value > 0.0) {
1708 old_value = vadj->value;
1709 last_value = vadj->value - vadj->step_increment;
1710 last_value = MAX(last_value, 0.0);
1712 textview_smooth_scroll_do(textview, old_value,
1714 prefs_common.scroll_step);
1719 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1721 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1722 GtkAdjustment *vadj = text->vadjustment;
1728 if (prefs_common.scroll_halfpage)
1729 page_incr = vadj->page_increment / 2;
1731 page_incr = vadj->page_increment;
1734 upper = vadj->upper - vadj->page_size;
1735 if (vadj->value < upper) {
1736 old_value = vadj->value;
1737 last_value = vadj->value + page_incr;
1738 last_value = MIN(last_value, upper);
1740 textview_smooth_scroll_do(textview, old_value,
1742 prefs_common.scroll_step);
1746 if (vadj->value > 0.0) {
1747 old_value = vadj->value;
1748 last_value = vadj->value - page_incr;
1749 last_value = MAX(last_value, 0.0);
1751 textview_smooth_scroll_do(textview, old_value,
1753 prefs_common.scroll_step);
1761 #define KEY_PRESS_EVENT_STOP() \
1762 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1765 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1768 SummaryView *summaryview = NULL;
1769 MessageView *messageview = textview->messageview;
1771 if (!event) return FALSE;
1772 if (messageview->mainwin)
1773 summaryview = messageview->mainwin->summaryview;
1775 switch (event->keyval) {
1790 summary_pass_key_press_event(summaryview, event);
1792 textview_scroll_page
1795 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1798 textview_scroll_page(textview, TRUE);
1801 textview_scroll_one_line
1802 (textview, (event->state &
1803 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1807 summary_pass_key_press_event(summaryview, event);
1812 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1813 KEY_PRESS_EVENT_STOP();
1814 mimeview_pass_key_press_event(messageview->mimeview,
1818 /* possible fall through */
1821 event->window != messageview->mainwin->window->window) {
1822 GdkEventKey tmpev = *event;
1824 tmpev.window = messageview->mainwin->window->window;
1825 KEY_PRESS_EVENT_STOP();
1826 gtk_widget_event(messageview->mainwin->window,
1827 (GdkEvent *)&tmpev);
1835 static gboolean textview_motion_notify(GtkWidget *widget,
1836 GdkEventMotion *event,
1839 textview_uri_update(textview, event->x, event->y);
1840 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1845 static gboolean textview_leave_notify(GtkWidget *widget,
1846 GdkEventCrossing *event,
1849 textview_uri_update(textview, -1, -1);
1854 static gboolean textview_visibility_notify(GtkWidget *widget,
1855 GdkEventVisibility *event,
1861 window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1862 GTK_TEXT_WINDOW_TEXT);
1864 /* check if occurred for the text window part */
1865 if (window != event->window)
1868 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1869 textview_uri_update(textview, wx, wy);
1874 static void textview_uri_update(TextView *textview, gint x, gint y)
1876 GtkTextBuffer *buffer;
1877 GtkTextIter start_iter, end_iter;
1878 RemoteURI *uri = NULL;
1880 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1882 if (x != -1 && y != -1) {
1888 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1889 GTK_TEXT_WINDOW_WIDGET,
1891 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1894 tags = gtk_text_iter_get_tags(&iter);
1895 for (cur = tags; cur != NULL; cur = cur->next) {
1896 GtkTextTag *tag = cur->data;
1899 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1900 if (!strcmp(name, "link")
1901 && textview_get_uri_range(textview, &iter, tag,
1902 &start_iter, &end_iter))
1903 uri = textview_get_uri_from_range(textview,
1915 if (uri != textview->uri_hover) {
1918 if (textview->uri_hover)
1919 gtk_text_buffer_remove_tag_by_name(buffer,
1921 &textview->uri_hover_start_iter,
1922 &textview->uri_hover_end_iter);
1924 textview->uri_hover = uri;
1926 textview->uri_hover_start_iter = start_iter;
1927 textview->uri_hover_end_iter = end_iter;
1930 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1931 GTK_TEXT_WINDOW_TEXT);
1932 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1934 TEXTVIEW_STATUSBAR_POP(textview);
1939 gtk_text_buffer_apply_tag_by_name(buffer,
1944 trimmed_uri = trim_string(uri->uri, 60);
1945 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1946 g_free(trimmed_uri);
1951 static gboolean textview_get_uri_range(TextView *textview,
1954 GtkTextIter *start_iter,
1955 GtkTextIter *end_iter)
1957 GtkTextIter _start_iter, _end_iter;
1960 if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1961 debug_print("Can't find end");
1965 _start_iter = _end_iter;
1966 if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1967 debug_print("Can't find start.");
1971 *start_iter = _start_iter;
1972 *end_iter = _end_iter;
1977 static RemoteURI *textview_get_uri_from_range(TextView *textview,
1980 GtkTextIter *start_iter,
1981 GtkTextIter *end_iter)
1983 gint start_pos, end_pos, cur_pos;
1984 RemoteURI *uri = NULL;
1987 start_pos = gtk_text_iter_get_offset(start_iter);
1988 end_pos = gtk_text_iter_get_offset(end_iter);
1989 cur_pos = gtk_text_iter_get_offset(iter);
1991 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1992 RemoteURI *uri_ = (RemoteURI *)cur->data;
1993 if (start_pos == uri_->start &&
1994 end_pos == uri_->end) {
1997 } else if (start_pos == uri_->start ||
1998 end_pos == uri_->end) {
1999 /* in case of contiguous links, textview_get_uri_range
2000 * returns a broader range (start of 1st link to end
2002 * In that case, correct link is the one covering
2005 if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
2015 static RemoteURI *textview_get_uri(TextView *textview,
2019 GtkTextIter start_iter, end_iter;
2020 RemoteURI *uri = NULL;
2022 if (textview_get_uri_range(textview, iter, tag, &start_iter,
2024 uri = textview_get_uri_from_range(textview, iter, tag,
2025 &start_iter, &end_iter);
2030 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2031 GdkEvent *event, GtkTextIter *iter,
2034 GdkEventButton *bevent;
2035 RemoteURI *uri = NULL;
2042 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2043 && event->type != GDK_MOTION_NOTIFY)
2046 uri = textview_get_uri(textview, iter, tag);
2050 bevent = (GdkEventButton *) event;
2052 /* doubleclick: open compose / add address / browser */
2053 if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2054 bevent->button == 2 || bevent->button == 3) {
2055 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2056 if (bevent->button == 3) {
2058 G_OBJECT(textview->mail_popup_menu),
2059 "menu_button", uri);
2060 gtk_menu_popup(GTK_MENU(textview->mail_popup_menu),
2061 NULL, NULL, NULL, NULL,
2062 bevent->button, bevent->time);
2064 PrefsAccount *account = NULL;
2066 if (textview->messageview && textview->messageview->msginfo &&
2067 textview->messageview->msginfo->folder) {
2068 FolderItem *folder_item;
2070 folder_item = textview->messageview->msginfo->folder;
2071 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2072 account = account_find_from_id(folder_item->prefs->default_account);
2074 compose_new(account, uri->uri + 7, NULL);
2078 if (bevent->button == 1 &&
2079 textview_uri_security_check(textview, uri) == TRUE)
2081 prefs_common.uri_cmd);
2082 else if (bevent->button == 3) {
2084 G_OBJECT(textview->link_popup_menu),
2085 "menu_button", uri);
2086 gtk_menu_popup(GTK_MENU(textview->link_popup_menu),
2087 NULL, NULL, NULL, NULL,
2088 bevent->button, bevent->time);
2098 *\brief Check to see if a web URL has been disguised as a different
2099 * URL (possible with HTML email).
2101 *\param uri The uri to check
2103 *\param textview The TextView the URL is contained in
2105 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2106 * it anyway, otherwise FALSE
2108 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2111 gboolean retval = TRUE;
2112 GtkTextBuffer *buffer;
2113 GtkTextIter start, end;
2115 if (is_uri_string(uri->uri) == FALSE)
2118 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2120 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2121 gtk_text_buffer_get_iter_at_offset(buffer, &end, uri->end);
2123 visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2125 if (visible_str == NULL)
2128 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2130 gchar *visible_uri_path;
2132 uri_path = get_uri_path(uri->uri);
2133 visible_uri_path = get_uri_path(visible_str);
2134 if (strcmp(uri_path, visible_uri_path) != 0)
2138 if (retval == FALSE) {
2142 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2143 "the apparent URL (%s).\n"
2145 uri->uri, visible_str);
2146 aval = alertpanel_with_type(_("Warning"), msg,
2147 GTK_STOCK_YES, GTK_STOCK_NO,
2148 NULL, NULL, ALERT_WARNING);
2150 if (aval == G_ALERTDEFAULT)
2154 g_free(visible_str);
2159 static void textview_uri_list_remove_all(GSList *uri_list)
2163 for (cur = uri_list; cur != NULL; cur = cur->next) {
2165 g_free(((RemoteURI *)cur->data)->uri);
2170 g_slist_free(uri_list);
2173 static void open_uri_cb (TextView *textview, guint action, void *data)
2175 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2180 if (textview_uri_security_check(textview, uri) == TRUE)
2182 prefs_common.uri_cmd);
2183 g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2187 static void copy_uri_cb (TextView *textview, guint action, void *data)
2189 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2194 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri, -1);
2195 g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2199 static void add_uri_to_addrbook_cb (TextView *textview, guint action, void *data)
2201 gchar *fromname, *fromaddress;
2202 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2208 fromaddress = g_strdup(uri->uri + 7);
2209 /* Hiroyuki: please put this function in utils.c! */
2210 fromname = procheader_get_fromname(fromaddress);
2211 extract_address(fromaddress);
2212 g_message("adding from textview %s <%s>", fromname, fromaddress);
2213 /* Add to address book - Match */
2214 addressbook_add_contact( fromname, fromaddress, NULL );
2216 g_free(fromaddress);
2220 static void mail_to_uri_cb (TextView *textview, guint action, void *data)
2222 PrefsAccount *account = NULL;
2223 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2228 if (textview->messageview && textview->messageview->msginfo &&
2229 textview->messageview->msginfo->folder) {
2230 FolderItem *folder_item;
2232 folder_item = textview->messageview->msginfo->folder;
2233 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2234 account = account_find_from_id(folder_item->prefs->default_account);
2236 compose_new(account, uri->uri + 7, NULL);
2239 static void copy_mail_to_uri_cb (TextView *textview, guint action, void *data)
2241 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2246 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri + 7, -1);
2247 g_object_set_data(G_OBJECT(textview->mail_popup_menu), "menu_button",