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 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
496 if (prefs_common.display_header && (charcount > 0))
497 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
499 textview_write_body(textview, mimeinfo);
503 static void recursive_add_parts(TextView *textview, GNode *node)
508 mimeinfo = (MimeInfo *) node->data;
510 textview_add_part(textview, mimeinfo);
512 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
513 (mimeinfo->type != MIMETYPE_MESSAGE))
516 if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
517 GNode * prefered_body;
525 prefered_body = NULL;
528 for (iter = g_node_first_child(node) ; iter != NULL ;
529 iter = g_node_next_sibling(iter)) {
534 submime = (MimeInfo *) iter->data;
535 if (submime->type == MIMETYPE_TEXT)
538 if (submime->subtype != NULL) {
539 if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
543 if (score > prefered_score) {
544 prefered_score = score;
545 prefered_body = iter;
549 if (prefered_body != NULL) {
550 recursive_add_parts(textview, prefered_body);
554 for (iter = g_node_first_child(node) ; iter != NULL ;
555 iter = g_node_next_sibling(iter)) {
556 recursive_add_parts(textview, iter);
561 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
563 g_return_if_fail(mimeinfo != NULL);
565 recursive_add_parts(textview, mimeinfo->node);
568 #define TEXT_INSERT(str) \
569 gtk_text_buffer_insert(buffer, &iter, str, -1)
571 void textview_show_error(TextView *textview)
574 GtkTextBuffer *buffer;
577 textview_set_font(textview, NULL);
578 textview_clear(textview);
580 text = GTK_TEXT_VIEW(textview->text);
581 buffer = gtk_text_view_get_buffer(text);
582 gtk_text_buffer_get_start_iter(buffer, &iter);
584 TEXT_INSERT(_("This message can't be displayed.\n"));
588 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
591 GtkTextBuffer *buffer;
594 if (!partinfo) return;
596 textview_set_font(textview, NULL);
597 textview_clear(textview);
599 text = GTK_TEXT_VIEW(textview->text);
600 buffer = gtk_text_view_get_buffer(text);
601 gtk_text_buffer_get_start_iter(buffer, &iter);
603 TEXT_INSERT(_("The following can be performed on this part by "));
604 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
606 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
607 TEXT_INSERT(_(" To display as text select 'Display as text' "));
608 TEXT_INSERT(_("(Shortcut key: 't')\n"));
609 TEXT_INSERT(_(" To open with an external program select 'Open' "));
610 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
611 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
612 TEXT_INSERT(_("mouse button),\n"));
613 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
619 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
624 const gchar *charset;
626 if (textview->messageview->forced_charset)
627 charset = textview->messageview->forced_charset;
629 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
631 textview_set_font(textview, charset);
633 conv = conv_code_converter_new(charset);
635 procmime_force_encoding(textview->messageview->forced_encoding);
637 textview->is_in_signature = FALSE;
639 procmime_decode_content(mimeinfo);
641 if (!g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
644 filename = procmime_get_tmp_file_name(mimeinfo);
645 if (procmime_get_part(filename, mimeinfo) == 0) {
646 tmpfp = fopen(filename, "rb");
647 textview_show_html(textview, tmpfp, conv);
652 } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
655 filename = procmime_get_tmp_file_name(mimeinfo);
656 if (procmime_get_part(filename, mimeinfo) == 0) {
657 tmpfp = fopen(filename, "rb");
658 textview_show_ertf(textview, tmpfp, conv);
664 tmpfp = fopen(mimeinfo->data.filename, "rb");
665 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
666 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
667 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
668 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
669 textview_write_line(textview, buf, conv);
673 conv_code_converter_destroy(conv);
674 procmime_force_encoding(0);
677 static void textview_show_html(TextView *textview, FILE *fp,
683 parser = html_parser_new(fp, conv);
684 g_return_if_fail(parser != NULL);
686 while ((str = html_parse(parser)) != NULL) {
687 if (parser->state == HTML_HREF) {
688 /* first time : get and copy the URL */
689 if (parser->href == NULL) {
690 /* ALF - the sylpheed html parser returns an empty string,
691 * if still inside an <a>, but already parsed past HREF */
692 str = strtok(str, " ");
694 parser->href = g_strdup(str);
695 /* the URL may (or not) be followed by the
697 str = strtok(NULL, "");
701 textview_write_link(textview, str, parser->href, NULL);
703 textview_write_line(textview, str, NULL);
705 html_parser_destroy(parser);
708 static void textview_show_ertf(TextView *textview, FILE *fp,
714 parser = ertf_parser_new(fp, conv);
715 g_return_if_fail(parser != NULL);
717 while ((str = ertf_parse(parser)) != NULL) {
718 textview_write_line(textview, str, NULL);
721 ertf_parser_destroy(parser);
724 /* get_uri_part() - retrieves a URI starting from scanpos.
725 Returns TRUE if succesful */
726 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
727 const gchar **bp, const gchar **ep)
731 g_return_val_if_fail(start != NULL, FALSE);
732 g_return_val_if_fail(scanpos != NULL, FALSE);
733 g_return_val_if_fail(bp != NULL, FALSE);
734 g_return_val_if_fail(ep != NULL, FALSE);
738 /* find end point of URI */
739 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
740 if (!isgraph(*(const guchar *)ep_) ||
741 !IS_ASCII(*(const guchar *)ep_) ||
742 strchr("()<>\"", *ep_))
746 /* no punctuation at end of string */
748 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
749 * should pass some URI type to this function and decide on that whether
750 * to perform punctuation stripping */
752 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
754 for (; ep_ - 1 > scanpos + 1 &&
755 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
766 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
768 return g_strndup(bp, ep - bp);
771 /* valid mail address characters */
772 #define IS_RFC822_CHAR(ch) \
777 !strchr("(),;<>\"", (ch)))
779 /* alphabet and number within 7bit ASCII */
780 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && isalnum(ch))
781 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
783 static GHashTable *create_domain_tab(void)
785 static const gchar *toplvl_domains [] = {
787 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
788 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
789 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
790 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
791 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
792 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
793 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
794 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
795 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
796 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
797 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
798 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
799 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
800 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
801 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
802 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
803 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
804 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
805 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
806 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
807 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
808 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
809 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
810 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
811 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
812 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
815 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
817 g_return_val_if_fail(htab, NULL);
818 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
819 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
823 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
825 const gint MAX_LVL_DOM_NAME_LEN = 6;
826 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
827 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
830 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
833 for (p = buf; p < m && first < last; *p++ = *first++)
837 return g_hash_table_lookup(tab, buf) != NULL;
840 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
841 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
842 const gchar **bp, const gchar **ep)
844 /* more complex than the uri part because we need to scan back and forward starting from
845 * the scan position. */
846 gboolean result = FALSE;
847 const gchar *bp_ = NULL;
848 const gchar *ep_ = NULL;
849 static GHashTable *dom_tab;
850 const gchar *last_dot = NULL;
851 const gchar *prelast_dot = NULL;
852 const gchar *last_tld_char = NULL;
854 /* the informative part of the email address (describing the name
855 * of the email address owner) may contain quoted parts. the
856 * closure stack stores the last encountered quotes. */
857 gchar closure_stack[128];
858 gchar *ptr = closure_stack;
860 g_return_val_if_fail(start != NULL, FALSE);
861 g_return_val_if_fail(scanpos != NULL, FALSE);
862 g_return_val_if_fail(bp != NULL, FALSE);
863 g_return_val_if_fail(ep != NULL, FALSE);
866 dom_tab = create_domain_tab();
867 g_return_val_if_fail(dom_tab, FALSE);
869 /* scan start of address */
870 for (bp_ = scanpos - 1;
871 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
874 /* TODO: should start with an alnum? */
876 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
879 if (bp_ != scanpos) {
880 /* scan end of address */
881 for (ep_ = scanpos + 1;
882 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
884 prelast_dot = last_dot;
886 if (*(last_dot + 1) == '.') {
887 if (prelast_dot == NULL)
889 last_dot = prelast_dot;
894 /* TODO: really should terminate with an alnum? */
895 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
900 if (last_dot == NULL)
903 last_dot = prelast_dot;
904 if (last_dot == NULL || (scanpos + 1 >= last_dot))
908 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
909 if (*last_tld_char == '?')
912 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
919 if (!result) return FALSE;
921 if (*ep_ && *(bp_ - 1) == '"' && *(ep_) == '"'
922 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
923 && IS_RFC822_CHAR(*(ep_ + 3))) {
924 /* this informative part with an @ in it is
925 * followed by the email address */
928 /* go to matching '>' (or next non-rfc822 char, like \n) */
929 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
932 /* include the bracket */
933 if (*ep_ == '>') ep_++;
935 /* include the leading quote */
943 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
944 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
947 /* see if this is <bracketed>; in this case we also scan for the informative part. */
948 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
951 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
952 #define IN_STACK() (ptr > closure_stack)
953 /* has underrun check */
954 #define POP_STACK() if(IN_STACK()) --ptr
955 /* has overrun check */
956 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
957 /* has underrun check */
958 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
962 /* scan for the informative part. */
963 for (bp_ -= 2; bp_ >= start; bp_--) {
964 /* if closure on the stack keep scanning */
965 if (PEEK_STACK() == *bp_) {
969 if (*bp_ == '\'' || *bp_ == '"') {
974 /* if nothing in the closure stack, do the special conditions
975 * the following if..else expression simply checks whether
976 * a token is acceptable. if not acceptable, the clause
977 * should terminate the loop with a 'break' */
980 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
981 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
982 /* hyphens are allowed, but only in
984 } else if (!strchr(",;:=?./+<>!&", *bp_)) {
985 /* but anything not being a punctiation
988 break; /* anything else is rejected */
1001 /* scan forward (should start with an alnum) */
1002 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
1012 #undef IS_ASCII_ALNUM
1013 #undef IS_RFC822_CHAR
1015 static gchar *make_email_string(const gchar *bp, const gchar *ep)
1017 /* returns a mailto: URI; mailto: is also used to detect the
1018 * uri type later on in the button_pressed signal handler */
1022 tmp = g_strndup(bp, ep - bp);
1023 result = g_strconcat("mailto:", tmp, NULL);
1029 static gchar *make_http_string(const gchar *bp, const gchar *ep)
1031 /* returns an http: URI; */
1035 tmp = g_strndup(bp, ep - bp);
1036 result = g_strconcat("http://", tmp, NULL);
1042 #define ADD_TXT_POS(bp_, ep_, pti_) \
1043 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1044 last = last->next; \
1045 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1046 last->next = NULL; \
1048 g_warning("alloc error scanning URIs\n"); \
1049 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1055 /* textview_make_clickable_parts() - colorizes clickable parts */
1056 static void textview_make_clickable_parts(TextView *textview,
1057 const gchar *fg_tag,
1058 const gchar *uri_tag,
1059 const gchar *linebuf)
1061 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1062 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1065 /* parse table - in order of priority */
1067 const gchar *needle; /* token */
1069 /* token search function */
1070 gchar *(*search) (const gchar *haystack,
1071 const gchar *needle);
1072 /* part parsing function */
1073 gboolean (*parse) (const gchar *start,
1074 const gchar *scanpos,
1077 /* part to URI function */
1078 gchar *(*build_uri) (const gchar *bp,
1082 static struct table parser[] = {
1083 {"http://", strcasestr, get_uri_part, make_uri_string},
1084 {"https://", strcasestr, get_uri_part, make_uri_string},
1085 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1086 {"www.", strcasestr, get_uri_part, make_http_string},
1087 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1088 {"@", strcasestr, get_email_part, make_email_string}
1090 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1093 const gchar *walk, *bp, *ep;
1096 const gchar *bp, *ep; /* text position */
1097 gint pti; /* index in parse table */
1098 struct txtpos *next; /* next */
1099 } head = {NULL, NULL, 0, NULL}, *last = &head;
1101 gtk_text_buffer_get_end_iter(buffer, &iter);
1103 /* parse for clickable parts, and build a list of begin and end positions */
1104 for (walk = linebuf, n = 0;;) {
1105 gint last_index = PARSE_ELEMS;
1106 gchar *scanpos = NULL;
1108 /* FIXME: this looks phony. scanning for anything in the parse table */
1109 for (n = 0; n < PARSE_ELEMS; n++) {
1112 tmp = parser[n].search(walk, parser[n].needle);
1114 if (scanpos == NULL || tmp < scanpos) {
1122 /* check if URI can be parsed */
1123 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1124 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1125 ADD_TXT_POS(bp, ep, last_index);
1129 strlen(parser[last_index].needle);
1134 /* colorize this line */
1136 const gchar *normal_text = linebuf;
1139 for (last = head.next; last != NULL;
1140 normal_text = last->ep, last = last->next) {
1142 uri = g_new(RemoteURI, 1);
1143 if (last->bp - normal_text > 0)
1144 gtk_text_buffer_insert_with_tags_by_name
1147 last->bp - normal_text,
1149 uri->uri = parser[last->pti].build_uri(last->bp,
1151 uri->start = gtk_text_iter_get_offset(&iter);
1152 gtk_text_buffer_insert_with_tags_by_name
1153 (buffer, &iter, last->bp, last->ep - last->bp,
1154 uri_tag, fg_tag, NULL);
1155 uri->end = gtk_text_iter_get_offset(&iter);
1156 textview->uri_list =
1157 g_slist_append(textview->uri_list, uri);
1161 gtk_text_buffer_insert_with_tags_by_name
1162 (buffer, &iter, normal_text, -1, fg_tag, NULL);
1164 gtk_text_buffer_insert_with_tags_by_name
1165 (buffer, &iter, linebuf, -1, fg_tag, NULL);
1171 static void textview_write_line(TextView *textview, const gchar *str,
1172 CodeConverter *conv)
1175 GtkTextBuffer *buffer;
1177 gchar buf[BUFFSIZE];
1179 gint quotelevel = -1;
1180 gchar quote_tag_str[10];
1182 text = GTK_TEXT_VIEW(textview->text);
1183 buffer = gtk_text_view_get_buffer(text);
1184 gtk_text_buffer_get_end_iter(buffer, &iter);
1187 strncpy2(buf, str, sizeof(buf));
1188 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1189 conv_utf8todisp(buf, sizeof(buf), str);
1192 //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1195 /* change color of quotation
1196 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1197 Up to 3 levels of quotations are detected, and each
1198 level is colored using a different color. */
1199 if (prefs_common.enable_color
1200 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1201 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1203 /* set up the correct foreground color */
1204 if (quotelevel > 2) {
1205 /* recycle colors */
1206 if (prefs_common.recycle_quote_colors)
1213 if (quotelevel == -1)
1216 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1217 "quote%d", quotelevel);
1218 fg_color = quote_tag_str;
1221 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1222 fg_color = "signature";
1223 textview->is_in_signature = TRUE;
1226 if (prefs_common.enable_color)
1227 textview_make_clickable_parts(textview, fg_color, "link", buf);
1229 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1232 void textview_write_link(TextView *textview, const gchar *str,
1233 const gchar *uri, CodeConverter *conv)
1235 GdkColor *link_color = NULL;
1237 GtkTextBuffer *buffer;
1239 gchar buf[BUFFSIZE];
1246 text = GTK_TEXT_VIEW(textview->text);
1247 buffer = gtk_text_view_get_buffer(text);
1248 gtk_text_buffer_get_end_iter(buffer, &iter);
1251 strncpy2(buf, str, sizeof(buf));
1252 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1253 conv_utf8todisp(buf, sizeof(buf), str);
1257 gtk_text_buffer_get_end_iter(buffer, &iter);
1259 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1260 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1262 if (prefs_common.enable_color) {
1263 link_color = &uri_color;
1265 r_uri = g_new(RemoteURI, 1);
1266 r_uri->uri = g_strdup(uri);
1267 r_uri->start = gtk_text_iter_get_offset(&iter);
1268 gtk_text_buffer_insert_with_tags_by_name
1269 (buffer, &iter, bufp, -1, "link", NULL);
1270 r_uri->end = gtk_text_iter_get_offset(&iter);
1271 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1274 void textview_clear(TextView *textview)
1276 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1277 GtkTextBuffer *buffer;
1279 buffer = gtk_text_view_get_buffer(text);
1280 gtk_text_buffer_set_text(buffer, "", -1);
1282 TEXTVIEW_STATUSBAR_POP(textview);
1283 textview_uri_list_remove_all(textview->uri_list);
1284 textview->uri_list = NULL;
1286 textview->body_pos = 0;
1289 void textview_destroy(TextView *textview)
1291 textview_uri_list_remove_all(textview->uri_list);
1292 textview->uri_list = NULL;
1297 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1299 textview->show_all_headers = all_headers;
1302 void textview_set_font(TextView *textview, const gchar *codeset)
1304 if (prefs_common.textfont) {
1305 PangoFontDescription *font_desc = NULL;
1307 font_desc = pango_font_description_from_string
1308 (prefs_common.textfont);
1310 gtk_widget_modify_font(textview->text, font_desc);
1311 pango_font_description_free(font_desc);
1314 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1315 prefs_common.line_space / 2);
1316 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1317 prefs_common.line_space / 2);
1320 void textview_set_text(TextView *textview, const gchar *text)
1323 GtkTextBuffer *buffer;
1325 g_return_if_fail(textview != NULL);
1326 g_return_if_fail(text != NULL);
1328 textview_clear(textview);
1330 view = GTK_TEXT_VIEW(textview->text);
1331 buffer = gtk_text_view_get_buffer(view);
1332 gtk_text_buffer_set_text(buffer, text, strlen(text));
1348 H_ORGANIZATION = 11,
1351 void textview_set_position(TextView *textview, gint pos)
1353 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1354 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1357 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1358 gtk_text_buffer_place_cursor(buffer, &iter);
1359 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1362 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1364 gchar buf[BUFFSIZE];
1365 GPtrArray *headers, *sorted_headers;
1366 GSList *disphdr_list;
1370 g_return_val_if_fail(fp != NULL, NULL);
1372 if (textview->show_all_headers)
1373 return procheader_get_header_array_asis(fp);
1375 if (!prefs_common.display_header) {
1376 while (fgets(buf, sizeof(buf), fp) != NULL)
1377 if (buf[0] == '\r' || buf[0] == '\n') break;
1381 headers = procheader_get_header_array_asis(fp);
1383 sorted_headers = g_ptr_array_new();
1385 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1386 disphdr_list = disphdr_list->next) {
1387 DisplayHeaderProp *dp =
1388 (DisplayHeaderProp *)disphdr_list->data;
1390 for (i = 0; i < headers->len; i++) {
1391 header = g_ptr_array_index(headers, i);
1393 if (procheader_headername_equal(header->name,
1396 procheader_header_free(header);
1398 g_ptr_array_add(sorted_headers, header);
1400 g_ptr_array_remove_index(headers, i);
1406 if (prefs_common.show_other_header) {
1407 for (i = 0; i < headers->len; i++) {
1408 header = g_ptr_array_index(headers, i);
1409 g_ptr_array_add(sorted_headers, header);
1411 g_ptr_array_free(headers, TRUE);
1413 procheader_header_array_destroy(headers);
1416 return sorted_headers;
1419 static void textview_show_header(TextView *textview, GPtrArray *headers)
1421 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1422 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1427 g_return_if_fail(headers != NULL);
1429 for (i = 0; i < headers->len; i++) {
1430 header = g_ptr_array_index(headers, i);
1431 g_return_if_fail(header->name != NULL);
1433 gtk_text_buffer_get_end_iter (buffer, &iter);
1434 gtk_text_buffer_insert_with_tags_by_name
1435 (buffer, &iter, header->name, -1,
1436 "header_title", "header", NULL);
1437 if (header->name[strlen(header->name) - 1] != ' ')
1438 gtk_text_buffer_insert_with_tags_by_name
1439 (buffer, &iter, " ", 1,
1440 "header_title", "header", NULL);
1442 if (procheader_headername_equal(header->name, "Subject") ||
1443 procheader_headername_equal(header->name, "From") ||
1444 procheader_headername_equal(header->name, "To") ||
1445 procheader_headername_equal(header->name, "Cc"))
1446 unfold_line(header->body);
1450 if (textview->text_is_mb == TRUE)
1451 conv_unreadable_locale(header->body);
1454 if (prefs_common.enable_color &&
1455 (procheader_headername_equal(header->name, "X-Mailer") ||
1456 procheader_headername_equal(header->name,
1458 strstr(header->body, "Sylpheed") != NULL) {
1459 gtk_text_buffer_get_end_iter (buffer, &iter);
1460 gtk_text_buffer_insert_with_tags_by_name
1461 (buffer, &iter, header->body, -1,
1462 "header", "emphasis", NULL);
1463 } else if (prefs_common.enable_color) {
1464 textview_make_clickable_parts(textview, "header", "link",
1467 textview_make_clickable_parts(textview, "header", NULL,
1470 gtk_text_buffer_get_end_iter (buffer, &iter);
1471 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1476 gboolean textview_search_string(TextView *textview, const gchar *str,
1479 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1480 GtkTextBuffer *buffer;
1481 GtkTextIter iter, end_iter;
1486 g_return_val_if_fail(str != NULL, FALSE);
1488 buffer = gtk_text_view_get_buffer(text);
1490 len = g_utf8_strlen(str, -1);
1491 g_return_val_if_fail(len >= 0, FALSE);
1493 mark = gtk_text_buffer_get_insert(buffer);
1494 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1495 pos = gtk_text_iter_get_offset(&iter);
1497 if ((pos = gtkut_text_buffer_find(buffer, pos, str, case_sens)) != -1) {
1498 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, pos);
1499 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos + len);
1500 gtk_text_buffer_select_range(buffer, &iter, &end_iter);
1501 gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1508 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1511 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1512 GtkTextBuffer *buffer;
1513 GtkTextIter iter, end_iter;
1518 glong items_read = 0, items_written = 0;
1519 GError *error = NULL;
1520 gboolean found = FALSE;
1522 g_return_val_if_fail(str != NULL, FALSE);
1524 buffer = gtk_text_view_get_buffer(text);
1526 wcs = g_utf8_to_ucs4(str, -1, &items_read, &items_written, &error);
1527 if (error != NULL) {
1528 g_warning("An error occured while converting a string from UTF-8 to UCS-4: %s\n", error->message);
1529 g_error_free(error);
1531 if (!wcs || items_written <= 0) return FALSE;
1532 len = (gint)items_written;
1534 mark = gtk_text_buffer_get_insert(buffer);
1535 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1537 while (gtk_text_iter_backward_char(&iter)) {
1538 pos = gtk_text_iter_get_offset(&iter);
1539 if (gtkut_text_buffer_match_string
1540 (buffer, pos, wcs, len, case_sens) == TRUE) {
1541 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1542 gtk_text_buffer_get_iter_at_offset
1543 (buffer, &end_iter, pos + len);
1544 gtk_text_buffer_select_range(buffer, &iter, &end_iter);
1545 gtk_text_view_scroll_to_mark
1546 (text, mark, 0.0, FALSE, 0.0, 0.0);
1556 void textview_scroll_one_line(TextView *textview, gboolean up)
1558 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1559 GtkAdjustment *vadj = text->vadjustment;
1562 if (prefs_common.enable_smooth_scroll) {
1563 textview_smooth_scroll_one_line(textview, up);
1568 upper = vadj->upper - vadj->page_size;
1569 if (vadj->value < upper) {
1570 vadj->value += vadj->step_increment;
1571 vadj->value = MIN(vadj->value, upper);
1572 g_signal_emit_by_name(G_OBJECT(vadj),
1573 "value_changed", 0);
1576 if (vadj->value > 0.0) {
1577 vadj->value -= vadj->step_increment;
1578 vadj->value = MAX(vadj->value, 0.0);
1579 g_signal_emit_by_name(G_OBJECT(vadj),
1580 "value_changed", 0);
1585 gboolean textview_scroll_page(TextView *textview, gboolean up)
1587 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1588 GtkAdjustment *vadj = text->vadjustment;
1592 if (prefs_common.enable_smooth_scroll)
1593 return textview_smooth_scroll_page(textview, up);
1595 if (prefs_common.scroll_halfpage)
1596 page_incr = vadj->page_increment / 2;
1598 page_incr = vadj->page_increment;
1601 upper = vadj->upper - vadj->page_size;
1602 if (vadj->value < upper) {
1603 vadj->value += page_incr;
1604 vadj->value = MIN(vadj->value, upper);
1605 g_signal_emit_by_name(G_OBJECT(vadj),
1606 "value_changed", 0);
1610 if (vadj->value > 0.0) {
1611 vadj->value -= page_incr;
1612 vadj->value = MAX(vadj->value, 0.0);
1613 g_signal_emit_by_name(G_OBJECT(vadj),
1614 "value_changed", 0);
1622 static void textview_smooth_scroll_do(TextView *textview,
1623 gfloat old_value, gfloat last_value,
1626 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1627 GtkAdjustment *vadj = text->vadjustment;
1632 if (old_value < last_value) {
1633 change_value = last_value - old_value;
1636 change_value = old_value - last_value;
1640 for (i = step; i <= change_value; i += step) {
1641 vadj->value = old_value + (up ? -i : i);
1642 g_signal_emit_by_name(G_OBJECT(vadj),
1643 "value_changed", 0);
1646 vadj->value = last_value;
1647 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1650 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1652 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1653 GtkAdjustment *vadj = text->vadjustment;
1659 upper = vadj->upper - vadj->page_size;
1660 if (vadj->value < upper) {
1661 old_value = vadj->value;
1662 last_value = vadj->value + vadj->step_increment;
1663 last_value = MIN(last_value, upper);
1665 textview_smooth_scroll_do(textview, old_value,
1667 prefs_common.scroll_step);
1670 if (vadj->value > 0.0) {
1671 old_value = vadj->value;
1672 last_value = vadj->value - vadj->step_increment;
1673 last_value = MAX(last_value, 0.0);
1675 textview_smooth_scroll_do(textview, old_value,
1677 prefs_common.scroll_step);
1682 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1684 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1685 GtkAdjustment *vadj = text->vadjustment;
1691 if (prefs_common.scroll_halfpage)
1692 page_incr = vadj->page_increment / 2;
1694 page_incr = vadj->page_increment;
1697 upper = vadj->upper - vadj->page_size;
1698 if (vadj->value < upper) {
1699 old_value = vadj->value;
1700 last_value = vadj->value + page_incr;
1701 last_value = MIN(last_value, upper);
1703 textview_smooth_scroll_do(textview, old_value,
1705 prefs_common.scroll_step);
1709 if (vadj->value > 0.0) {
1710 old_value = vadj->value;
1711 last_value = vadj->value - page_incr;
1712 last_value = MAX(last_value, 0.0);
1714 textview_smooth_scroll_do(textview, old_value,
1716 prefs_common.scroll_step);
1724 #define KEY_PRESS_EVENT_STOP() \
1725 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1728 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1731 SummaryView *summaryview = NULL;
1732 MessageView *messageview = textview->messageview;
1734 if (!event) return FALSE;
1735 if (messageview->mainwin)
1736 summaryview = messageview->mainwin->summaryview;
1738 switch (event->keyval) {
1753 summary_pass_key_press_event(summaryview, event);
1755 textview_scroll_page
1758 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1761 textview_scroll_page(textview, TRUE);
1764 textview_scroll_one_line
1765 (textview, (event->state &
1766 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1770 summary_pass_key_press_event(summaryview, event);
1775 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1776 KEY_PRESS_EVENT_STOP();
1777 mimeview_pass_key_press_event(messageview->mimeview,
1781 /* possible fall through */
1784 event->window != messageview->mainwin->window->window) {
1785 GdkEventKey tmpev = *event;
1787 tmpev.window = messageview->mainwin->window->window;
1788 KEY_PRESS_EVENT_STOP();
1789 gtk_widget_event(messageview->mainwin->window,
1790 (GdkEvent *)&tmpev);
1798 static gboolean textview_motion_notify(GtkWidget *widget,
1799 GdkEventMotion *event,
1802 textview_uri_update(textview, event->x, event->y);
1803 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1808 static gboolean textview_leave_notify(GtkWidget *widget,
1809 GdkEventCrossing *event,
1812 textview_uri_update(textview, -1, -1);
1817 static gboolean textview_visibility_notify(GtkWidget *widget,
1818 GdkEventVisibility *event,
1824 window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1825 GTK_TEXT_WINDOW_TEXT);
1827 /* check if occurred for the text window part */
1828 if (window != event->window)
1831 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1832 textview_uri_update(textview, wx, wy);
1837 static void textview_uri_update(TextView *textview, gint x, gint y)
1839 GtkTextBuffer *buffer;
1840 GtkTextIter start_iter, end_iter;
1841 RemoteURI *uri = NULL;
1843 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1845 if (x != -1 && y != -1) {
1851 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1852 GTK_TEXT_WINDOW_WIDGET,
1854 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1857 tags = gtk_text_iter_get_tags(&iter);
1858 for (cur = tags; cur != NULL; cur = cur->next) {
1859 GtkTextTag *tag = cur->data;
1862 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1863 if (!strcmp(name, "link")
1864 && textview_get_uri_range(textview, &iter, tag,
1865 &start_iter, &end_iter))
1866 uri = textview_get_uri_from_range(textview,
1878 if (uri != textview->uri_hover) {
1881 if (textview->uri_hover)
1882 gtk_text_buffer_remove_tag_by_name(buffer,
1884 &textview->uri_hover_start_iter,
1885 &textview->uri_hover_end_iter);
1887 textview->uri_hover = uri;
1889 textview->uri_hover_start_iter = start_iter;
1890 textview->uri_hover_end_iter = end_iter;
1893 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1894 GTK_TEXT_WINDOW_TEXT);
1895 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1897 TEXTVIEW_STATUSBAR_POP(textview);
1902 gtk_text_buffer_apply_tag_by_name(buffer,
1907 trimmed_uri = trim_string(uri->uri, 60);
1908 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1909 g_free(trimmed_uri);
1914 static gboolean textview_get_uri_range(TextView *textview,
1917 GtkTextIter *start_iter,
1918 GtkTextIter *end_iter)
1920 GtkTextIter _start_iter, _end_iter;
1923 if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1924 debug_print("Can't find end");
1928 _start_iter = _end_iter;
1929 if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1930 debug_print("Can't find start.");
1934 *start_iter = _start_iter;
1935 *end_iter = _end_iter;
1940 static RemoteURI *textview_get_uri_from_range(TextView *textview,
1943 GtkTextIter *start_iter,
1944 GtkTextIter *end_iter)
1946 gint start_pos, end_pos, cur_pos;
1947 RemoteURI *uri = NULL;
1950 start_pos = gtk_text_iter_get_offset(start_iter);
1951 end_pos = gtk_text_iter_get_offset(end_iter);
1952 cur_pos = gtk_text_iter_get_offset(iter);
1954 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1955 RemoteURI *uri_ = (RemoteURI *)cur->data;
1956 if (start_pos == uri_->start &&
1957 end_pos == uri_->end) {
1960 } else if (start_pos == uri_->start ||
1961 end_pos == uri_->end) {
1962 /* in case of contiguous links, textview_get_uri_range
1963 * returns a broader range (start of 1st link to end
1965 * In that case, correct link is the one covering
1968 if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
1978 static RemoteURI *textview_get_uri(TextView *textview,
1982 GtkTextIter start_iter, end_iter;
1983 RemoteURI *uri = NULL;
1985 if (textview_get_uri_range(textview, iter, tag, &start_iter,
1987 uri = textview_get_uri_from_range(textview, iter, tag,
1988 &start_iter, &end_iter);
1993 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1994 GdkEvent *event, GtkTextIter *iter,
1997 GdkEventButton *bevent;
1998 RemoteURI *uri = NULL;
2005 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2006 && event->type != GDK_MOTION_NOTIFY)
2009 uri = textview_get_uri(textview, iter, tag);
2013 bevent = (GdkEventButton *) event;
2015 /* doubleclick: open compose / add address / browser */
2016 if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2017 bevent->button == 2 || bevent->button == 3) {
2018 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2019 if (bevent->button == 3) {
2021 G_OBJECT(textview->mail_popup_menu),
2022 "menu_button", uri);
2023 gtk_menu_popup(GTK_MENU(textview->mail_popup_menu),
2024 NULL, NULL, NULL, NULL,
2025 bevent->button, bevent->time);
2027 PrefsAccount *account = NULL;
2029 if (textview->messageview && textview->messageview->msginfo &&
2030 textview->messageview->msginfo->folder) {
2031 FolderItem *folder_item;
2033 folder_item = textview->messageview->msginfo->folder;
2034 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2035 account = account_find_from_id(folder_item->prefs->default_account);
2037 compose_new(account, uri->uri + 7, NULL);
2041 if (bevent->button == 1 &&
2042 textview_uri_security_check(textview, uri) == TRUE)
2044 prefs_common.uri_cmd);
2045 else if (bevent->button == 3) {
2047 G_OBJECT(textview->link_popup_menu),
2048 "menu_button", uri);
2049 gtk_menu_popup(GTK_MENU(textview->link_popup_menu),
2050 NULL, NULL, NULL, NULL,
2051 bevent->button, bevent->time);
2061 *\brief Check to see if a web URL has been disguised as a different
2062 * URL (possible with HTML email).
2064 *\param uri The uri to check
2066 *\param textview The TextView the URL is contained in
2068 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2069 * it anyway, otherwise FALSE
2071 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2074 gboolean retval = TRUE;
2075 GtkTextBuffer *buffer;
2076 GtkTextIter start, end;
2078 if (is_uri_string(uri->uri) == FALSE)
2081 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2083 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2084 gtk_text_buffer_get_iter_at_offset(buffer, &end, uri->end);
2086 visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2088 if (visible_str == NULL)
2091 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2093 gchar *visible_uri_path;
2095 uri_path = get_uri_path(uri->uri);
2096 visible_uri_path = get_uri_path(visible_str);
2097 if (strcmp(uri_path, visible_uri_path) != 0)
2101 if (retval == FALSE) {
2105 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2106 "the apparent URL (%s).\n"
2108 uri->uri, visible_str);
2109 aval = alertpanel_with_type(_("Warning"), msg,
2110 GTK_STOCK_YES, GTK_STOCK_NO,
2111 NULL, NULL, ALERT_WARNING);
2113 if (aval == G_ALERTDEFAULT)
2117 g_free(visible_str);
2122 static void textview_uri_list_remove_all(GSList *uri_list)
2126 for (cur = uri_list; cur != NULL; cur = cur->next) {
2128 g_free(((RemoteURI *)cur->data)->uri);
2133 g_slist_free(uri_list);
2136 static void open_uri_cb (TextView *textview, guint action, void *data)
2138 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2143 if (textview_uri_security_check(textview, uri) == TRUE)
2145 prefs_common.uri_cmd);
2146 g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2150 static void copy_uri_cb (TextView *textview, guint action, void *data)
2152 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2157 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri, -1);
2158 g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2162 static void add_uri_to_addrbook_cb (TextView *textview, guint action, void *data)
2164 gchar *fromname, *fromaddress;
2165 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2171 fromaddress = g_strdup(uri->uri + 7);
2172 /* Hiroyuki: please put this function in utils.c! */
2173 fromname = procheader_get_fromname(fromaddress);
2174 extract_address(fromaddress);
2175 g_message("adding from textview %s <%s>", fromname, fromaddress);
2176 /* Add to address book - Match */
2177 addressbook_add_contact( fromname, fromaddress, NULL );
2179 g_free(fromaddress);
2183 static void mail_to_uri_cb (TextView *textview, guint action, void *data)
2185 PrefsAccount *account = NULL;
2186 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2191 if (textview->messageview && textview->messageview->msginfo &&
2192 textview->messageview->msginfo->folder) {
2193 FolderItem *folder_item;
2195 folder_item = textview->messageview->msginfo->folder;
2196 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2197 account = account_find_from_id(folder_item->prefs->default_account);
2199 compose_new(account, uri->uri + 7, NULL);
2202 static void copy_mail_to_uri_cb (TextView *textview, guint action, void *data)
2204 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2209 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri + 7, -1);
2210 g_object_set_data(G_OBJECT(textview->mail_popup_menu), "menu_button",