2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2004 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtkvbox.h>
30 #include <gtk/gtkscrolledwindow.h>
31 #include <gtk/gtksignal.h>
39 #include "summaryview.h"
40 #include "procheader.h"
41 #include "prefs_common.h"
49 #include "addressbook.h"
50 #include "displayheader.h"
53 #include "alertpanel.h"
55 typedef struct _RemoteURI RemoteURI;
65 static GdkColor quote_colors[3] = {
66 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
67 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
68 {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
71 static GdkColor signature_color = {
78 static GdkColor uri_color = {
85 static GdkColor emphasis_color = {
93 static GdkColor error_color = {
101 static GdkColor good_sig_color = {
108 static GdkColor nocheck_sig_color = {
115 static GdkColor bad_sig_color = {
122 static void textview_show_ertf (TextView *textview,
124 CodeConverter *conv);
125 static void textview_add_part (TextView *textview,
127 static void textview_add_parts (TextView *textview,
129 static void textview_write_body (TextView *textview,
131 const gchar *charset);
132 static void textview_show_html (TextView *textview,
134 CodeConverter *conv);
136 static void textview_write_line (TextView *textview,
138 CodeConverter *conv);
139 static void textview_write_link (TextView *textview,
142 CodeConverter *conv);
144 static GPtrArray *textview_scan_header (TextView *textview,
146 static void textview_show_header (TextView *textview,
149 static gint textview_key_pressed (GtkWidget *widget,
152 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
153 GdkEvent *event, GtkTextIter *iter,
155 static void textview_smooth_scroll_do (TextView *textview,
159 static void textview_smooth_scroll_one_line (TextView *textview,
161 static gboolean textview_smooth_scroll_page (TextView *textview,
164 static gboolean textview_uri_security_check (TextView *textview,
166 static void textview_uri_list_remove_all (GSList *uri_list);
169 static void populate_popup(GtkTextView *textview, GtkMenu *menu,
172 gtk_menu_detach(menu);
175 TextView *textview_create(void)
179 GtkWidget *scrolledwin;
181 GtkTextBuffer *buffer;
182 GtkClipboard *clipboard;
183 PangoFontDescription *font_desc = NULL;
185 debug_print("Creating text view...\n");
186 textview = g_new0(TextView, 1);
188 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
189 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
190 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
191 gtk_widget_set_size_request(scrolledwin, prefs_common.mainview_width, -1);
192 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
195 /* create GtkSText widgets for single-byte and multi-byte character */
196 text = gtk_text_view_new();
197 gtk_widget_show(text);
198 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
199 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
200 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
201 g_signal_connect(G_OBJECT(text), "populate-popup",
202 G_CALLBACK(populate_popup), NULL);
205 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
206 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
207 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
209 gtk_widget_ensure_style(text);
211 if (prefs_common.normalfont)
212 font_desc = pango_font_description_from_string
213 (prefs_common.normalfont);
215 gtk_widget_modify_font(text, font_desc);
217 pango_font_description_free(font_desc);
219 gtk_widget_ref(scrolledwin);
221 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
223 g_signal_connect(G_OBJECT(text), "key_press_event",
224 G_CALLBACK(textview_key_pressed),
227 gtk_widget_show(scrolledwin);
229 vbox = gtk_vbox_new(FALSE, 0);
230 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
232 gtk_widget_show(vbox);
234 textview->vbox = vbox;
235 textview->scrolledwin = scrolledwin;
236 textview->text = text;
237 textview->uri_list = NULL;
238 textview->body_pos = 0;
239 textview->show_all_headers = FALSE;
240 textview->last_buttonpress = GDK_NOTHING;
241 textview->show_url_msgid = 0;
246 static void textview_create_tags(GtkTextView *text, TextView *textview)
248 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
251 gtk_text_buffer_create_tag(buffer, "header",
252 "pixels-above-lines", 0,
253 "pixels-above-lines-set", TRUE,
254 "pixels-below-lines", 0,
255 "pixels-below-lines-set", TRUE,
257 "left-margin-set", TRUE,
259 gtk_text_buffer_create_tag(buffer, "header_title",
260 "font", prefs_common.boldfont,
262 gtk_text_buffer_create_tag(buffer, "quote0",
263 "foreground-gdk", "e_colors[0],
265 gtk_text_buffer_create_tag(buffer, "quote1",
266 "foreground-gdk", "e_colors[1],
268 gtk_text_buffer_create_tag(buffer, "quote2",
269 "foreground-gdk", "e_colors[2],
271 gtk_text_buffer_create_tag(buffer, "emphasis",
272 "foreground-gdk", &emphasis_color,
274 gtk_text_buffer_create_tag(buffer, "signature",
275 "foreground-gdk", &signature_color,
277 tag = gtk_text_buffer_create_tag(buffer, "link",
278 "foreground-gdk", &uri_color,
281 gtk_text_buffer_create_tag(buffer, "good-signature",
282 "foreground-gdk", &good_sig_color,
284 gtk_text_buffer_create_tag(buffer, "bad-signature",
285 "foreground-gdk", &bad_sig_color,
287 gtk_text_buffer_create_tag(buffer, "nocheck-signature",
288 "foreground-gdk", &nocheck_sig_color,
290 #endif /*USE_GPGME */
292 g_signal_connect(G_OBJECT(tag), "event",
293 G_CALLBACK(textview_uri_button_pressed), textview);
296 void textview_init(TextView *textview)
300 gtkut_widget_disable_theme_engine(textview->text);
302 textview_update_message_colors();
303 textview_set_all_headers(textview, FALSE);
304 textview_set_font(textview, NULL);
306 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
309 void textview_update_message_colors(void)
311 GdkColor black = {0, 0, 0, 0};
313 if (prefs_common.enable_color) {
314 /* grab the quote colors, converting from an int to a GdkColor */
315 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
317 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
319 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
321 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
323 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
326 quote_colors[0] = quote_colors[1] = quote_colors[2] =
327 uri_color = emphasis_color = signature_color = black;
331 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
335 const gchar *charset = NULL;
337 if ((fp = fopen(file, "rb")) == NULL) {
338 FILE_OP_ERROR(file, "fopen");
342 if (textview->messageview->forced_charset)
343 charset = textview->messageview->forced_charset;
344 else if (prefs_common.force_charset)
345 charset = prefs_common.force_charset;
347 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
349 textview_set_font(textview, charset);
350 textview_clear(textview);
354 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) perror("fseek");
355 headers = textview_scan_header(textview, fp);
357 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
358 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
361 textview_show_header(textview, headers);
362 procheader_header_array_destroy(headers);
364 gtk_text_buffer_get_end_iter(buffer, &iter);
365 textview->body_pos = gtk_text_iter_get_offset(&iter);
368 textview_add_parts(textview, mimeinfo);
372 textview_set_position(textview, 0);
375 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
378 const gchar *charset = NULL;
380 g_return_if_fail(mimeinfo != NULL);
381 g_return_if_fail(fp != NULL);
383 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
384 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
385 textview_clear(textview);
386 textview_add_parts(textview, mimeinfo);
390 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
393 headers = textview_scan_header(textview, fp);
395 if (textview->messageview->forced_charset)
396 charset = textview->messageview->forced_charset;
397 else if (prefs_common.force_charset)
398 charset = prefs_common.force_charset;
400 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
402 textview_set_font(textview, charset);
404 textview_clear(textview);
408 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
409 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
412 textview_show_header(textview, headers);
413 procheader_header_array_destroy(headers);
415 gtk_text_buffer_get_end_iter(buffer, &iter);
416 textview->body_pos = gtk_text_iter_get_offset(&iter);
417 if (!mimeinfo->main) {
418 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
422 if (mimeinfo->type == MIMETYPE_MULTIPART)
423 textview_add_parts(textview, mimeinfo);
425 textview_write_body(textview, mimeinfo, charset);
429 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
432 GtkTextBuffer *buffer;
435 const gchar *charset = NULL;
436 GPtrArray *headers = NULL;
440 g_return_if_fail(mimeinfo != NULL);
441 text = GTK_TEXT_VIEW(textview->text);
442 buffer = gtk_text_view_get_buffer(text);
443 charcount = gtk_text_buffer_get_char_count(buffer);
444 gtk_text_buffer_get_end_iter(buffer, &iter);
446 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
448 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
451 fp = fopen(mimeinfo->filename, "rb");
452 fseek(fp, mimeinfo->offset, SEEK_SET);
453 headers = textview_scan_header(textview, fp);
456 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
457 textview_show_header(textview, headers);
458 procheader_header_array_destroy(headers);
464 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
466 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
468 g_snprintf(buf, sizeof(buf), "\n[%s %s/%s (%d bytes)]\n",
470 procmime_get_type_str(mimeinfo->type),
471 mimeinfo->subtype, mimeinfo->length);
473 g_snprintf(buf, sizeof(buf), "\n[%s/%s (%d bytes)]\n",
474 procmime_get_type_str(mimeinfo->type),
475 mimeinfo->subtype, mimeinfo->length);
477 if (mimeinfo->type != MIMETYPE_TEXT) {
478 gtk_text_buffer_insert(buffer, &iter, buf, -1);
479 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
480 if (prefs_common.display_header && (charcount > 0))
481 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
482 if (textview->messageview->forced_charset)
483 charset = textview->messageview->forced_charset;
484 else if (prefs_common.force_charset)
485 charset = prefs_common.force_charset;
487 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
489 textview_write_body(textview, mimeinfo, charset);
494 static gboolean add_parts_func(GNode *node, gpointer data)
496 MimeInfo *mimeinfo = (MimeInfo *) node->data;
497 TextView *textview = (TextView *) data;
499 g_return_val_if_fail(mimeinfo != NULL, FALSE);
501 textview_add_part(textview, mimeinfo);
506 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
508 g_return_if_fail(mimeinfo != NULL);
510 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, add_parts_func, textview);
514 static void recursive_add_parts(TextView *textview, GNode *node)
519 mimeinfo = (MimeInfo *) node->data;
521 textview_add_part(textview, mimeinfo);
523 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
524 (mimeinfo->type != MIMETYPE_MESSAGE))
527 if (strcasecmp(mimeinfo->subtype, "alternative") == 0) {
528 GNode * prefered_body;
536 prefered_body = NULL;
539 for(iter = g_node_first_child(node) ; iter != NULL ;
540 iter = g_node_next_sibling(iter)) {
545 submime = (MimeInfo *) iter->data;
546 if (submime->type == MIMETYPE_TEXT)
549 if (submime->subtype != NULL) {
550 if (strcasecmp(submime->subtype, "plain") == 0)
554 if (score > prefered_score) {
555 prefered_score = score;
556 prefered_body = iter;
560 if (prefered_body != NULL) {
561 recursive_add_parts(textview, prefered_body);
565 for(iter = g_node_first_child(node) ; iter != NULL ;
566 iter = g_node_next_sibling(iter)) {
567 recursive_add_parts(textview, iter);
572 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
574 g_return_if_fail(mimeinfo != NULL);
576 recursive_add_parts(textview, mimeinfo->node);
579 #define TEXT_INSERT(str) \
580 gtk_text_buffer_insert(buffer, &iter, str, -1)
582 void textview_show_error(TextView *textview)
585 GtkTextBuffer *buffer;
588 textview_set_font(textview, NULL);
589 textview_clear(textview);
591 text = GTK_TEXT_VIEW(textview->text);
592 buffer = gtk_text_view_get_buffer(text);
593 gtk_text_buffer_get_start_iter(buffer, &iter);
595 TEXT_INSERT(_("This message can't be displayed.\n"));
599 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
602 GtkTextBuffer *buffer;
605 if (!partinfo) return;
607 textview_set_font(textview, NULL);
608 textview_clear(textview);
610 text = GTK_TEXT_VIEW(textview->text);
611 buffer = gtk_text_view_get_buffer(text);
612 gtk_text_buffer_get_start_iter(buffer, &iter);
614 TEXT_INSERT(_("The following can be performed on this part by "));
615 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
617 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
618 TEXT_INSERT(_(" To display as text select 'Display as text' "));
619 TEXT_INSERT(_("(Shortcut key: 't')\n"));
620 TEXT_INSERT(_(" To open with an external program select 'Open' "));
621 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
622 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
623 TEXT_INSERT(_("mouse button),\n"));
624 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
630 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
631 const gchar *charset)
637 conv = conv_code_converter_new(charset);
639 textview->is_in_signature = FALSE;
641 if(mimeinfo->encoding_type != ENC_BINARY &&
642 mimeinfo->encoding_type != ENC_7BIT &&
643 mimeinfo->encoding_type != ENC_8BIT)
644 procmime_decode_content(mimeinfo);
646 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
649 filename = procmime_get_tmp_file_name(mimeinfo);
650 if (procmime_get_part(filename, mimeinfo) == 0) {
651 tmpfp = fopen(filename, "rb");
652 textview_show_html(textview, tmpfp, conv);
657 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
660 filename = procmime_get_tmp_file_name(mimeinfo);
661 if (procmime_get_part(filename, mimeinfo) == 0) {
662 tmpfp = fopen(filename, "rb");
663 textview_show_ertf(textview, tmpfp, conv);
669 tmpfp = fopen(mimeinfo->filename, "rb");
670 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
671 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
672 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
673 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
674 textview_write_line(textview, buf, conv);
678 conv_code_converter_destroy(conv);
681 static void textview_show_html(TextView *textview, FILE *fp,
687 parser = html_parser_new(fp, conv);
688 g_return_if_fail(parser != NULL);
690 while ((str = html_parse(parser)) != NULL) {
691 if (parser->state == HTML_HREF) {
692 /* first time : get and copy the URL */
693 if (parser->href == NULL) {
694 /* ALF - the sylpheed html parser returns an empty string,
695 * if still inside an <a>, but already parsed past HREF */
696 str = strtok(str, " ");
698 parser->href = strdup(str);
699 /* the URL may (or not) be followed by the
701 str = strtok(NULL, "");
705 textview_write_link(textview, str, parser->href, NULL);
707 textview_write_line(textview, str, NULL);
709 html_parser_destroy(parser);
712 static void textview_show_ertf(TextView *textview, FILE *fp,
718 parser = ertf_parser_new(fp, conv);
719 g_return_if_fail(parser != NULL);
721 while ((str = ertf_parse(parser)) != NULL) {
722 textview_write_line(textview, str, NULL);
725 ertf_parser_destroy(parser);
728 /* get_uri_part() - retrieves a URI starting from scanpos.
729 Returns TRUE if succesful */
730 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
731 const gchar **bp, const gchar **ep)
735 g_return_val_if_fail(start != NULL, FALSE);
736 g_return_val_if_fail(scanpos != NULL, FALSE);
737 g_return_val_if_fail(bp != NULL, FALSE);
738 g_return_val_if_fail(ep != NULL, FALSE);
742 /* find end point of URI */
743 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
744 if (!isgraph(*(const guchar *)ep_) ||
745 !isascii(*(const guchar *)ep_) ||
746 strchr("()<>\"", *ep_))
750 /* no punctuation at end of string */
752 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
753 * should pass some URI type to this function and decide on that whether
754 * to perform punctuation stripping */
756 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
758 for (; ep_ - 1 > scanpos + 1 &&
759 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
770 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
772 return g_strndup(bp, ep - bp);
775 /* valid mail address characters */
776 #define IS_RFC822_CHAR(ch) \
781 !strchr("(),;<>\"", (ch)))
783 /* alphabet and number within 7bit ASCII */
784 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
785 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
787 static GHashTable *create_domain_tab(void)
789 static const gchar *toplvl_domains [] = {
791 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
792 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
793 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
794 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
795 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
796 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
797 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
798 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
799 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
800 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
801 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
802 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
803 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
804 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
805 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
806 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
807 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
808 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
809 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
810 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
811 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
812 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
813 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
814 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
815 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
816 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
819 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
821 g_return_val_if_fail(htab, NULL);
822 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
823 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
827 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
829 const gint MAX_LVL_DOM_NAME_LEN = 6;
830 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
831 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
834 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
837 for (p = buf; p < m && first < last; *p++ = *first++)
841 return g_hash_table_lookup(tab, buf) != NULL;
844 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
845 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
846 const gchar **bp, const gchar **ep)
848 /* more complex than the uri part because we need to scan back and forward starting from
849 * the scan position. */
850 gboolean result = FALSE;
851 const gchar *bp_ = NULL;
852 const gchar *ep_ = NULL;
853 static GHashTable *dom_tab;
854 const gchar *last_dot = NULL;
855 const gchar *prelast_dot = NULL;
856 const gchar *last_tld_char = NULL;
858 /* the informative part of the email address (describing the name
859 * of the email address owner) may contain quoted parts. the
860 * closure stack stores the last encountered quotes. */
861 gchar closure_stack[128];
862 gchar *ptr = closure_stack;
864 g_return_val_if_fail(start != NULL, FALSE);
865 g_return_val_if_fail(scanpos != NULL, FALSE);
866 g_return_val_if_fail(bp != NULL, FALSE);
867 g_return_val_if_fail(ep != NULL, FALSE);
870 dom_tab = create_domain_tab();
871 g_return_val_if_fail(dom_tab, FALSE);
873 /* scan start of address */
874 for (bp_ = scanpos - 1;
875 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
878 /* TODO: should start with an alnum? */
880 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
883 if (bp_ != scanpos) {
884 /* scan end of address */
885 for (ep_ = scanpos + 1;
886 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
888 prelast_dot = last_dot;
890 if (*(last_dot + 1) == '.') {
891 if (prelast_dot == NULL)
893 last_dot = prelast_dot;
898 /* TODO: really should terminate with an alnum? */
899 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
904 if (last_dot == NULL)
907 last_dot = prelast_dot;
908 if (last_dot == NULL || (scanpos + 1 >= last_dot))
912 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
913 if (*last_tld_char == '?')
916 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
923 if (!result) return FALSE;
925 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
926 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
929 /* see if this is <bracketed>; in this case we also scan for the informative part. */
930 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
933 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
934 #define IN_STACK() (ptr > closure_stack)
935 /* has underrun check */
936 #define POP_STACK() if(IN_STACK()) --ptr
937 /* has overrun check */
938 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
939 /* has underrun check */
940 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
944 /* scan for the informative part. */
945 for (bp_ -= 2; bp_ >= start; bp_--) {
946 /* if closure on the stack keep scanning */
947 if (PEEK_STACK() == *bp_) {
951 if (*bp_ == '\'' || *bp_ == '"') {
956 /* if nothing in the closure stack, do the special conditions
957 * the following if..else expression simply checks whether
958 * a token is acceptable. if not acceptable, the clause
959 * should terminate the loop with a 'break' */
962 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
963 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
964 /* hyphens are allowed, but only in
966 } else if (!ispunct(*bp_)) {
967 /* but anything not being a punctiation
970 break; /* anything else is rejected */
983 /* scan forward (should start with an alnum) */
984 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
994 #undef IS_ASCII_ALNUM
995 #undef IS_RFC822_CHAR
997 static gchar *make_email_string(const gchar *bp, const gchar *ep)
999 /* returns a mailto: URI; mailto: is also used to detect the
1000 * uri type later on in the button_pressed signal handler */
1004 tmp = g_strndup(bp, ep - bp);
1005 result = g_strconcat("mailto:", tmp, NULL);
1011 #define ADD_TXT_POS(bp_, ep_, pti_) \
1012 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1013 last = last->next; \
1014 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1015 last->next = NULL; \
1017 g_warning("alloc error scanning URIs\n"); \
1018 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1024 /* textview_make_clickable_parts() - colorizes clickable parts */
1025 static void textview_make_clickable_parts(TextView *textview,
1026 const gchar *fg_tag,
1027 const gchar *uri_tag,
1028 const gchar *linebuf)
1030 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1031 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1034 /* parse table - in order of priority */
1036 const gchar *needle; /* token */
1038 /* token search function */
1039 gchar *(*search) (const gchar *haystack,
1040 const gchar *needle);
1041 /* part parsing function */
1042 gboolean (*parse) (const gchar *start,
1043 const gchar *scanpos,
1046 /* part to URI function */
1047 gchar *(*build_uri) (const gchar *bp,
1051 static struct table parser[] = {
1052 {"http://", strcasestr, get_uri_part, make_uri_string},
1053 {"https://", strcasestr, get_uri_part, make_uri_string},
1054 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1055 {"www.", strcasestr, get_uri_part, make_uri_string},
1056 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1057 {"@", strcasestr, get_email_part, make_email_string}
1059 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1062 const gchar *walk, *bp, *ep;
1065 const gchar *bp, *ep; /* text position */
1066 gint pti; /* index in parse table */
1067 struct txtpos *next; /* next */
1068 } head = {NULL, NULL, 0, NULL}, *last = &head;
1070 gtk_text_buffer_get_end_iter(buffer, &iter);
1072 /* parse for clickable parts, and build a list of begin and end positions */
1073 for (walk = linebuf, n = 0;;) {
1074 gint last_index = PARSE_ELEMS;
1075 gchar *scanpos = NULL;
1077 /* FIXME: this looks phony. scanning for anything in the parse table */
1078 for (n = 0; n < PARSE_ELEMS; n++) {
1081 tmp = parser[n].search(walk, parser[n].needle);
1083 if (scanpos == NULL || tmp < scanpos) {
1091 /* check if URI can be parsed */
1092 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1093 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1094 ADD_TXT_POS(bp, ep, last_index);
1098 strlen(parser[last_index].needle);
1103 /* colorize this line */
1105 const gchar *normal_text = linebuf;
1108 for (last = head.next; last != NULL;
1109 normal_text = last->ep, last = last->next) {
1112 uri = g_new(RemoteURI, 1);
1113 if (last->bp - normal_text > 0)
1114 gtk_text_buffer_insert_with_tags_by_name
1117 last->bp - normal_text,
1119 uri->uri = parser[last->pti].build_uri(last->bp,
1121 uri->start = gtk_text_iter_get_offset(&iter);
1122 gtk_text_buffer_insert_with_tags_by_name
1124 last->bp, last->ep - last->bp,
1126 uri->end = gtk_text_iter_get_offset(&iter);
1127 textview->uri_list =
1128 g_slist_append(textview->uri_list, uri);
1132 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1136 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1144 static void textview_write_line(TextView *textview, const gchar *str,
1145 CodeConverter *conv)
1148 GtkTextBuffer *buffer;
1150 gchar buf[BUFFSIZE];
1152 gint quotelevel = -1;
1153 gchar quote_tag_str[10];
1155 text = GTK_TEXT_VIEW(textview->text);
1156 buffer = gtk_text_view_get_buffer(text);
1157 gtk_text_buffer_get_end_iter(buffer, &iter);
1162 if (textview->text_is_mb)
1163 conv_localetodisp(buf, sizeof(buf), str);
1165 strncpy2(buf, str, sizeof(buf));
1166 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1167 conv_localetodisp(buf, sizeof(buf), str);
1168 else if (textview->text_is_mb)
1169 conv_unreadable_locale(buf);
1172 strncpy2(buf, str, sizeof(buf));
1173 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1174 conv_localetodisp(buf, sizeof(buf), str);
1178 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1181 /* change color of quotation
1182 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1183 Up to 3 levels of quotations are detected, and each
1184 level is colored using a different color. */
1185 if (prefs_common.enable_color
1186 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1187 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1189 /* set up the correct foreground color */
1190 if (quotelevel > 2) {
1191 /* recycle colors */
1192 if (prefs_common.recycle_quote_colors)
1199 if (quotelevel == -1)
1202 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1203 "quote%d", quotelevel);
1204 fg_color = quote_tag_str;
1207 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1208 fg_color = "signature";
1209 textview->is_in_signature = TRUE;
1212 if (prefs_common.enable_color)
1213 textview_make_clickable_parts(textview, fg_color, "link", buf);
1215 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1218 void textview_write_link(TextView *textview, const gchar *str,
1219 const gchar *uri, CodeConverter *conv)
1221 GdkColor *link_color = NULL;
1223 GtkTextBuffer *buffer;
1225 gchar buf[BUFFSIZE];
1232 text = GTK_TEXT_VIEW(textview->text);
1233 buffer = gtk_text_view_get_buffer(text);
1234 gtk_text_buffer_get_end_iter(buffer, &iter);
1239 if (textview->text_is_mb)
1240 conv_localetodisp(buf, sizeof(buf), str);
1242 strncpy2(buf, str, sizeof(buf));
1243 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1244 conv_localetodisp(buf, sizeof(buf), str);
1245 else if (textview->text_is_mb)
1246 conv_unreadable_locale(buf);
1249 strncpy2(buf, str, sizeof(buf));
1250 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1251 conv_localetodisp(buf, sizeof(buf), str);
1256 gtk_text_buffer_get_end_iter(buffer, &iter);
1258 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1259 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1261 if (prefs_common.enable_color) {
1262 link_color = &uri_color;
1264 r_uri = g_new(RemoteURI, 1);
1265 r_uri->uri = g_strdup(uri);
1266 r_uri->start = gtk_text_iter_get_offset(&iter);
1267 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1269 r_uri->end = gtk_text_iter_get_offset(&iter);
1270 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1273 void textview_clear(TextView *textview)
1275 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1276 GtkTextBuffer *buffer;
1278 buffer = gtk_text_view_get_buffer(text);
1279 gtk_text_buffer_set_text(buffer, "\0", -1);
1281 textview_uri_list_remove_all(textview->uri_list);
1282 textview->uri_list = NULL;
1284 textview->body_pos = 0;
1287 void textview_destroy(TextView *textview)
1289 textview_uri_list_remove_all(textview->uri_list);
1290 textview->uri_list = NULL;
1295 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1297 textview->show_all_headers = all_headers;
1300 void textview_set_font(TextView *textview, const gchar *codeset)
1302 if (prefs_common.textfont) {
1303 PangoFontDescription *font_desc = NULL;
1305 if (prefs_common.textfont)
1306 font_desc = pango_font_description_from_string
1307 (prefs_common.textfont);
1309 gtk_widget_modify_font(textview->text, font_desc);
1310 pango_font_description_free(font_desc);
1313 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1314 prefs_common.line_space / 2);
1315 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1316 prefs_common.line_space / 2);
1317 if (prefs_common.head_space) {
1318 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1320 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1324 void textview_set_text(TextView *textview, const gchar *text)
1327 GtkTextBuffer *buffer;
1329 g_return_if_fail(textview != NULL);
1330 g_return_if_fail(text != NULL);
1332 textview_clear(textview);
1334 view = GTK_TEXT_VIEW(textview->text);
1335 buffer = gtk_text_view_get_buffer(view);
1336 gtk_text_buffer_set_text(buffer, text, strlen(text));
1352 H_ORGANIZATION = 11,
1355 void textview_set_position(TextView *textview, gint pos)
1357 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1358 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1361 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1362 gtk_text_buffer_place_cursor(buffer, &iter);
1365 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1367 gchar buf[BUFFSIZE];
1368 GPtrArray *headers, *sorted_headers;
1369 GSList *disphdr_list;
1373 g_return_val_if_fail(fp != NULL, NULL);
1375 if (textview->show_all_headers)
1376 return procheader_get_header_array_asis(fp);
1378 if (!prefs_common.display_header) {
1379 while (fgets(buf, sizeof(buf), fp) != NULL)
1380 if (buf[0] == '\r' || buf[0] == '\n') break;
1384 headers = procheader_get_header_array_asis(fp);
1386 sorted_headers = g_ptr_array_new();
1388 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1389 disphdr_list = disphdr_list->next) {
1390 DisplayHeaderProp *dp =
1391 (DisplayHeaderProp *)disphdr_list->data;
1393 for (i = 0; i < headers->len; i++) {
1394 header = g_ptr_array_index(headers, i);
1396 if (procheader_headername_equal(header->name,
1399 procheader_header_free(header);
1401 g_ptr_array_add(sorted_headers, header);
1403 g_ptr_array_remove_index(headers, i);
1409 if (prefs_common.show_other_header) {
1410 for (i = 0; i < headers->len; i++) {
1411 header = g_ptr_array_index(headers, i);
1412 g_ptr_array_add(sorted_headers, header);
1414 g_ptr_array_free(headers, TRUE);
1416 procheader_header_array_destroy(headers);
1419 return sorted_headers;
1422 static void textview_show_header(TextView *textview, GPtrArray *headers)
1424 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1425 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1430 g_return_if_fail(headers != NULL);
1432 for (i = 0; i < headers->len; i++) {
1433 header = g_ptr_array_index(headers, i);
1434 g_return_if_fail(header->name != NULL);
1436 gtk_text_buffer_get_end_iter (buffer, &iter);
1437 gtk_text_buffer_insert_with_tags_by_name
1438 (buffer, &iter, header->name, -1,
1439 "header_title", "header", NULL);
1440 if (header->name[strlen(header->name) - 1] != ' ')
1443 gtk_stext_insert(text, textview->boldfont,
1444 NULL, NULL, " ", 1);
1446 gtk_text_buffer_insert_with_tags_by_name
1447 (buffer, &iter, " ", 1,
1448 "header_title", "header", NULL);
1451 if (procheader_headername_equal(header->name, "Subject") ||
1452 procheader_headername_equal(header->name, "From") ||
1453 procheader_headername_equal(header->name, "To") ||
1454 procheader_headername_equal(header->name, "Cc"))
1455 unfold_line(header->body);
1459 if (textview->text_is_mb == TRUE)
1460 conv_unreadable_locale(header->body);
1463 if (prefs_common.enable_color &&
1464 (procheader_headername_equal(header->name, "X-Mailer") ||
1465 procheader_headername_equal(header->name,
1467 strstr(header->body, "Sylpheed") != NULL) {
1468 gtk_text_buffer_get_end_iter (buffer, &iter);
1469 gtk_text_buffer_insert_with_tags_by_name
1470 (buffer, &iter, header->body, -1,
1471 "header", "emphasis", NULL);
1472 } else if (prefs_common.enable_color) {
1473 textview_make_clickable_parts(textview, "header", "link",
1476 textview_make_clickable_parts(textview, "header", NULL,
1479 gtk_text_buffer_get_end_iter (buffer, &iter);
1480 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1485 gboolean textview_search_string(TextView *textview, const gchar *str,
1488 #warning FIXME_GTK2 /* currently, these search functions ignores case_sens */
1490 GtkSText *text = GTK_STEXT(textview->text);
1494 g_return_val_if_fail(str != NULL, FALSE);
1496 len = get_mbs_len(str);
1497 g_return_val_if_fail(len >= 0, FALSE);
1499 pos = textview->cur_pos;
1500 if (pos < textview->body_pos)
1501 pos = textview->body_pos;
1503 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1504 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1505 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1506 textview_set_position(textview, pos + len);
1512 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1513 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1515 GtkTextIter iter, start, end, *pos;
1517 gint insert_offset, selbound_offset;
1519 /* reset selection */
1520 mark = gtk_text_buffer_get_mark(buffer, "insert");
1521 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1522 insert_offset = gtk_text_iter_get_offset(&start);
1523 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1524 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1525 selbound_offset = gtk_text_iter_get_offset(&end);
1527 pos = insert_offset > selbound_offset ? &start : &end;
1528 gtk_text_buffer_place_cursor(buffer, pos);
1531 mark = gtk_text_buffer_get_insert(buffer);
1532 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1533 found = gtk_text_iter_forward_search(&iter, str,
1534 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1535 &start, &end, NULL);
1537 gtk_text_buffer_place_cursor(buffer, &start);
1538 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", &end);
1539 mark = gtk_text_buffer_get_mark(buffer, "insert");
1540 gtk_text_view_scroll_mark_onscreen(text, mark);
1547 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1552 GtkSText *text = GTK_STEXT(textview->text);
1557 gboolean found = FALSE;
1559 g_return_val_if_fail(str != NULL, FALSE);
1561 wcs = strdup_mbstowcs(str);
1562 g_return_val_if_fail(wcs != NULL, FALSE);
1564 pos = textview->cur_pos;
1565 text_len = gtk_stext_get_length(text);
1566 if (text_len - textview->body_pos < len) {
1570 if (pos <= textview->body_pos || text_len - pos < len)
1571 pos = text_len - len;
1573 for (; pos >= textview->body_pos; pos--) {
1574 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1576 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1577 gtk_editable_select_region(GTK_EDITABLE(text),
1579 textview_set_position(textview, pos - 1);
1583 if (pos == textview->body_pos) break;
1589 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1590 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1592 GtkTextIter iter, start, end, *pos;
1594 gint insert_offset, selbound_offset;
1596 /* reset selection */
1597 mark = gtk_text_buffer_get_mark(buffer, "insert");
1598 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1599 insert_offset = gtk_text_iter_get_offset(&start);
1600 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1601 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1602 selbound_offset = gtk_text_iter_get_offset(&end);
1604 pos = insert_offset < selbound_offset ? &start : &end;
1605 gtk_text_buffer_place_cursor(buffer, pos);
1608 mark = gtk_text_buffer_get_insert(buffer);
1609 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1610 found = gtk_text_iter_backward_search(&iter, str,
1611 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1612 &start, &end, NULL);
1614 gtk_text_buffer_place_cursor(buffer, &end);
1615 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", &start);
1616 mark = gtk_text_buffer_get_mark(buffer, "insert");
1617 gtk_text_view_scroll_mark_onscreen(text, mark);
1624 void textview_scroll_one_line(TextView *textview, gboolean up)
1626 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1627 GtkAdjustment *vadj = text->vadjustment;
1630 if (prefs_common.enable_smooth_scroll) {
1631 textview_smooth_scroll_one_line(textview, up);
1636 upper = vadj->upper - vadj->page_size;
1637 if (vadj->value < upper) {
1639 vadj->step_increment * 4;
1641 MIN(vadj->value, upper);
1642 g_signal_emit_by_name(G_OBJECT(vadj),
1643 "value_changed", 0);
1646 if (vadj->value > 0.0) {
1648 vadj->step_increment * 4;
1650 MAX(vadj->value, 0.0);
1651 g_signal_emit_by_name(G_OBJECT(vadj),
1652 "value_changed", 0);
1657 gboolean textview_scroll_page(TextView *textview, gboolean up)
1659 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1660 GtkAdjustment *vadj = text->vadjustment;
1664 if (prefs_common.enable_smooth_scroll)
1665 return textview_smooth_scroll_page(textview, up);
1667 if (prefs_common.scroll_halfpage)
1668 page_incr = vadj->page_increment / 2;
1670 page_incr = vadj->page_increment;
1673 upper = vadj->upper - vadj->page_size;
1674 if (vadj->value < upper) {
1675 vadj->value += page_incr;
1676 vadj->value = MIN(vadj->value, upper);
1677 g_signal_emit_by_name(G_OBJECT(vadj),
1678 "value_changed", 0);
1682 if (vadj->value > 0.0) {
1683 vadj->value -= page_incr;
1684 vadj->value = MAX(vadj->value, 0.0);
1685 g_signal_emit_by_name(G_OBJECT(vadj),
1686 "value_changed", 0);
1694 static void textview_smooth_scroll_do(TextView *textview,
1695 gfloat old_value, gfloat last_value,
1698 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1699 GtkAdjustment *vadj = text->vadjustment;
1704 if (old_value < last_value) {
1705 change_value = last_value - old_value;
1708 change_value = old_value - last_value;
1713 /* gdk_key_repeat_disable(); */
1715 for (i = step; i <= change_value; i += step) {
1716 vadj->value = old_value + (up ? -i : i);
1717 g_signal_emit_by_name(G_OBJECT(vadj),
1718 "value_changed", 0);
1721 vadj->value = last_value;
1722 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1725 /* gdk_key_repeat_restore(); */
1728 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1730 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1731 GtkAdjustment *vadj = text->vadjustment;
1737 upper = vadj->upper - vadj->page_size;
1738 if (vadj->value < upper) {
1739 old_value = vadj->value;
1740 last_value = vadj->value +
1741 vadj->step_increment * 4;
1742 last_value = MIN(last_value, upper);
1744 textview_smooth_scroll_do(textview, old_value,
1746 prefs_common.scroll_step);
1749 if (vadj->value > 0.0) {
1750 old_value = vadj->value;
1751 last_value = vadj->value -
1752 vadj->step_increment * 4;
1753 last_value = MAX(last_value, 0.0);
1755 textview_smooth_scroll_do(textview, old_value,
1757 prefs_common.scroll_step);
1762 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1764 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1765 GtkAdjustment *vadj = text->vadjustment;
1771 if (prefs_common.scroll_halfpage)
1772 page_incr = vadj->page_increment / 2;
1774 page_incr = vadj->page_increment;
1777 upper = vadj->upper - vadj->page_size;
1778 if (vadj->value < upper) {
1779 old_value = vadj->value;
1780 last_value = vadj->value + page_incr;
1781 last_value = MIN(last_value, upper);
1783 textview_smooth_scroll_do(textview, old_value,
1785 prefs_common.scroll_step);
1789 if (vadj->value > 0.0) {
1790 old_value = vadj->value;
1791 last_value = vadj->value - page_incr;
1792 last_value = MAX(last_value, 0.0);
1794 textview_smooth_scroll_do(textview, old_value,
1796 prefs_common.scroll_step);
1806 #define KEY_PRESS_EVENT_STOP() \
1807 if (gtk_signal_n_emissions_by_name \
1808 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1809 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1810 "key_press_event"); \
1813 #define KEY_PRESS_EVENT_STOP() \
1814 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1818 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1821 SummaryView *summaryview = NULL;
1822 MessageView *messageview = textview->messageview;
1824 if (!event) return FALSE;
1825 if (messageview->mainwin)
1826 summaryview = messageview->mainwin->summaryview;
1828 switch (event->keyval) {
1843 summary_pass_key_press_event(summaryview, event);
1845 textview_scroll_page(textview, FALSE);
1848 textview_scroll_page(textview, TRUE);
1851 textview_scroll_one_line(textview,
1852 (event->state & GDK_MOD1_MASK) != 0);
1856 summary_pass_key_press_event(summaryview, event);
1861 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1862 KEY_PRESS_EVENT_STOP();
1863 mimeview_pass_key_press_event(messageview->mimeview,
1867 /* possible fall through */
1870 event->window != messageview->mainwin->window->window) {
1871 GdkEventKey tmpev = *event;
1873 tmpev.window = messageview->mainwin->window->window;
1874 KEY_PRESS_EVENT_STOP();
1875 gtk_widget_event(messageview->mainwin->window,
1876 (GdkEvent *)&tmpev);
1884 static gint show_url_timeout_cb(gpointer data)
1886 TextView *textview = (TextView *)data;
1888 if (textview->messageview->mainwin)
1889 if (textview->show_url_msgid)
1890 gtk_statusbar_remove(GTK_STATUSBAR(
1891 textview->messageview->mainwin->statusbar),
1892 textview->messageview->mainwin->folderview_cid,
1893 textview->show_url_msgid);
1898 *\brief Check to see if a web URL has been disguised as a different
1899 * URL (possible with HTML email).
1901 *\param uri The uri to check
1903 *\param textview The TextView the URL is contained in
1905 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1906 * it anyway, otherwise FALSE
1908 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1911 gboolean retval = TRUE;
1913 if (g_strncasecmp(uri->uri, "http:", 5) &&
1914 g_strncasecmp(uri->uri, "https:", 6) &&
1915 g_strncasecmp(uri->uri, "www.", 4))
1918 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1921 if (clicked_str == NULL)
1924 if (strcmp(clicked_str, uri->uri) &&
1925 (!g_strncasecmp(clicked_str, "http:", 5) ||
1926 !g_strncasecmp(clicked_str, "https:", 6) ||
1927 !g_strncasecmp(clicked_str, "www.", 4))) {
1931 /* allow uri->uri == http://somewhere.com
1932 and clicked_str == somewhere.com */
1933 str = g_strconcat("http://", clicked_str, NULL);
1935 if (!g_strcasecmp(str, uri->uri))
1940 if (retval == FALSE) {
1944 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1945 "the apparent URL (%s). \n"
1947 uri->uri, clicked_str);
1948 resp = alertpanel(_("Warning"),
1954 if (resp == G_ALERTDEFAULT)
1957 g_free(clicked_str);
1961 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1962 GdkEvent *event, GtkTextIter *iter,
1965 GtkTextIter start_iter, end_iter;
1966 gint start_pos, end_pos;
1967 GdkEventButton *bevent;
1971 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
1972 && event->type != GDK_MOTION_NOTIFY)
1975 bevent = (GdkEventButton *) event;
1977 /* get start and end positions */
1979 if(!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) {
1980 debug_print("Can't find start.");
1983 start_pos = gtk_text_iter_get_offset(&start_iter);
1986 if(!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) {
1987 debug_print("Can't find end");
1990 end_pos = gtk_text_iter_get_offset(&end_iter);
1992 /* search current uri */
1993 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1994 RemoteURI *uri = (RemoteURI *)cur->data;
1996 if (start_pos != uri->start || end_pos != uri->end)
1999 trimmed_uri = trim_string(uri->uri, 60);
2000 /* hover or single click: display url in statusbar */
2001 if (event->type == GDK_MOTION_NOTIFY
2002 || (event->type == GDK_BUTTON_PRESS && bevent->button == 1)) {
2003 if (textview->messageview->mainwin) {
2004 if (textview->show_url_msgid) {
2005 gtk_timeout_remove(textview->show_url_timeout_tag);
2006 gtk_statusbar_remove(GTK_STATUSBAR(
2007 textview->messageview->mainwin->statusbar),
2008 textview->messageview->mainwin->folderview_cid,
2009 textview->show_url_msgid);
2010 textview->show_url_msgid = 0;
2012 textview->show_url_msgid = gtk_statusbar_push(
2013 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
2014 textview->messageview->mainwin->folderview_cid,
2016 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
2017 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
2021 /* doubleclick: open compose / add address / browser */
2022 if ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) ||
2023 bevent->button == 2 || bevent->button == 3) {
2024 if (!g_strncasecmp(uri->uri, "mailto:", 7))
2025 if (bevent->button == 3) {
2026 gchar *fromname, *fromaddress;
2029 fromaddress = g_strdup(uri->uri + 7);
2030 /* Hiroyuki: please put this function in utils.c! */
2031 fromname = procheader_get_fromname(fromaddress);
2032 extract_address(fromaddress);
2033 g_message("adding from textview %s <%s>", fromname, fromaddress);
2034 /* Add to address book - Match */
2035 addressbook_add_contact( fromname, fromaddress, NULL );
2037 g_free(fromaddress);
2040 PrefsAccount *account = NULL;
2041 FolderItem *folder_item;
2043 if (textview->messageview && textview->messageview->mainwin
2044 && textview->messageview->mainwin->summaryview
2045 && textview->messageview->mainwin->summaryview->folder_item) {
2046 folder_item = textview->messageview->mainwin->summaryview->folder_item;
2047 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2048 account = account_find_from_id(folder_item->prefs->default_account);
2050 compose_new(account, uri->uri + 7, NULL);
2053 if (uri_security_check(uri, textview) == TRUE)
2054 open_uri(uri->uri, prefs_common.uri_cmd);
2057 g_free(trimmed_uri);
2064 *\brief Check to see if a web URL has been disguised as a different
2065 * URL (possible with HTML email).
2067 *\param uri The uri to check
2069 *\param textview The TextView the URL is contained in
2071 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2072 * it anyway, otherwise FALSE
2074 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2077 gboolean retval = TRUE;
2079 if (is_uri_string(uri->uri) == FALSE)
2082 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
2083 uri->start, uri->end);
2084 if (visible_str == NULL)
2087 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2089 gchar *visible_uri_path;
2091 uri_path = get_uri_path(uri->uri);
2092 visible_uri_path = get_uri_path(visible_str);
2093 if (strcmp(uri_path, visible_uri_path) != 0)
2097 if (retval == FALSE) {
2101 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2102 "the apparent URL (%s).\n"
2104 uri->uri, visible_str);
2105 aval = alertpanel(_("Warning"), msg, _("Yes"), _("No"), NULL);
2107 if (aval == G_ALERTDEFAULT)
2111 g_free(visible_str);
2116 static void textview_uri_list_remove_all(GSList *uri_list)
2120 for (cur = uri_list; cur != NULL; cur = cur->next) {
2122 g_free(((RemoteURI *)cur->data)->uri);
2127 g_slist_free(uri_list);