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 = {
102 #define TEXTVIEW_STATUSBAR_PUSH(textview, str) \
104 gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
105 textview->messageview->statusbar_cid, str); \
108 #define TEXTVIEW_STATUSBAR_POP(textview) \
110 gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
111 textview->messageview->statusbar_cid); \
114 static void textview_show_ertf (TextView *textview,
116 CodeConverter *conv);
117 static void textview_add_part (TextView *textview,
119 static void textview_add_parts (TextView *textview,
121 static void textview_write_body (TextView *textview,
123 const gchar *charset);
124 static void textview_show_html (TextView *textview,
126 CodeConverter *conv);
128 static void textview_write_line (TextView *textview,
130 CodeConverter *conv);
131 static void textview_write_link (TextView *textview,
134 CodeConverter *conv);
136 static GPtrArray *textview_scan_header (TextView *textview,
138 static void textview_show_header (TextView *textview,
141 static gint textview_key_pressed (GtkWidget *widget,
144 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
145 GdkEvent *event, GtkTextIter *iter,
147 static void textview_smooth_scroll_do (TextView *textview,
151 static void textview_smooth_scroll_one_line (TextView *textview,
153 static gboolean textview_smooth_scroll_page (TextView *textview,
156 static gboolean textview_uri_security_check (TextView *textview,
158 static void textview_uri_list_remove_all (GSList *uri_list);
161 static void populate_popup(GtkTextView *textview, GtkMenu *menu,
164 gtk_menu_detach(menu);
167 TextView *textview_create(void)
171 GtkWidget *scrolledwin;
173 GtkTextBuffer *buffer;
174 GtkClipboard *clipboard;
175 PangoFontDescription *font_desc = NULL;
177 debug_print("Creating text view...\n");
178 textview = g_new0(TextView, 1);
180 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
181 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
182 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
183 gtk_widget_set_size_request(scrolledwin, prefs_common.mainview_width, -1);
184 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
187 /* create GtkSText widgets for single-byte and multi-byte character */
188 text = gtk_text_view_new();
189 gtk_widget_show(text);
190 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
191 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
192 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
193 g_signal_connect(G_OBJECT(text), "populate-popup",
194 G_CALLBACK(populate_popup), NULL);
197 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
198 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
199 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
201 gtk_widget_ensure_style(text);
203 if (prefs_common.normalfont)
204 font_desc = pango_font_description_from_string
205 (prefs_common.normalfont);
207 gtk_widget_modify_font(text, font_desc);
209 pango_font_description_free(font_desc);
211 gtk_widget_ref(scrolledwin);
213 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
215 g_signal_connect(G_OBJECT(text), "key_press_event",
216 G_CALLBACK(textview_key_pressed),
219 gtk_widget_show(scrolledwin);
221 vbox = gtk_vbox_new(FALSE, 0);
222 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
224 gtk_widget_show(vbox);
226 textview->vbox = vbox;
227 textview->scrolledwin = scrolledwin;
228 textview->text = text;
229 textview->uri_list = NULL;
230 textview->body_pos = 0;
231 textview->show_all_headers = FALSE;
232 textview->last_buttonpress = GDK_NOTHING;
233 textview->show_url_msgid = 0;
238 static void textview_create_tags(GtkTextView *text, TextView *textview)
240 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
243 gtk_text_buffer_create_tag(buffer, "header",
244 "pixels-above-lines", 0,
245 "pixels-above-lines-set", TRUE,
246 "pixels-below-lines", 0,
247 "pixels-below-lines-set", TRUE,
249 "left-margin-set", TRUE,
251 gtk_text_buffer_create_tag(buffer, "header_title",
252 "font", prefs_common.boldfont,
254 gtk_text_buffer_create_tag(buffer, "quote0",
255 "foreground-gdk", "e_colors[0],
257 gtk_text_buffer_create_tag(buffer, "quote1",
258 "foreground-gdk", "e_colors[1],
260 gtk_text_buffer_create_tag(buffer, "quote2",
261 "foreground-gdk", "e_colors[2],
263 gtk_text_buffer_create_tag(buffer, "emphasis",
264 "foreground-gdk", &emphasis_color,
266 gtk_text_buffer_create_tag(buffer, "signature",
267 "foreground-gdk", &signature_color,
269 tag = gtk_text_buffer_create_tag(buffer, "link",
270 "foreground-gdk", &uri_color,
273 g_signal_connect(G_OBJECT(tag), "event",
274 G_CALLBACK(textview_uri_button_pressed), textview);
277 void textview_init(TextView *textview)
279 gtkut_widget_disable_theme_engine(textview->text);
280 textview_update_message_colors();
281 textview_set_all_headers(textview, FALSE);
282 textview_set_font(textview, NULL);
284 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
287 void textview_update_message_colors(void)
289 GdkColor black = {0, 0, 0, 0};
291 if (prefs_common.enable_color) {
292 /* grab the quote colors, converting from an int to a GdkColor */
293 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
295 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
297 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
299 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
301 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
304 quote_colors[0] = quote_colors[1] = quote_colors[2] =
305 uri_color = emphasis_color = signature_color = black;
309 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
313 const gchar *charset = NULL;
315 if ((fp = fopen(file, "rb")) == NULL) {
316 FILE_OP_ERROR(file, "fopen");
320 if (textview->messageview->forced_charset)
321 charset = textview->messageview->forced_charset;
322 else if (prefs_common.force_charset)
323 charset = prefs_common.force_charset;
325 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
327 textview_set_font(textview, charset);
328 textview_clear(textview);
330 textview_add_parts(textview, mimeinfo);
334 textview_set_position(textview, 0);
337 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
340 const gchar *charset = NULL;
342 g_return_if_fail(mimeinfo != NULL);
343 g_return_if_fail(fp != NULL);
345 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
346 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
347 textview_clear(textview);
348 textview_add_parts(textview, mimeinfo);
352 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
355 if (textview->messageview->forced_charset)
356 charset = textview->messageview->forced_charset;
357 else if (prefs_common.force_charset)
358 charset = prefs_common.force_charset;
360 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
362 textview_set_font(textview, charset);
364 textview_clear(textview);
366 if (mimeinfo->type == MIMETYPE_MULTIPART)
367 textview_add_parts(textview, mimeinfo);
369 textview_write_body(textview, mimeinfo, charset);
373 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
376 GtkTextBuffer *buffer;
379 const gchar *charset = NULL;
380 GPtrArray *headers = NULL;
384 g_return_if_fail(mimeinfo != NULL);
385 text = GTK_TEXT_VIEW(textview->text);
386 buffer = gtk_text_view_get_buffer(text);
387 charcount = gtk_text_buffer_get_char_count(buffer);
388 gtk_text_buffer_get_end_iter(buffer, &iter);
390 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
392 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
395 fp = fopen(mimeinfo->filename, "rb");
396 fseek(fp, mimeinfo->offset, SEEK_SET);
397 headers = textview_scan_header(textview, fp);
400 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
401 textview_show_header(textview, headers);
402 procheader_header_array_destroy(headers);
408 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
410 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
412 g_snprintf(buf, sizeof(buf), "\n[%s %s/%s (%d bytes)]\n",
414 procmime_get_type_str(mimeinfo->type),
415 mimeinfo->subtype, mimeinfo->length);
417 g_snprintf(buf, sizeof(buf), "\n[%s/%s (%d bytes)]\n",
418 procmime_get_type_str(mimeinfo->type),
419 mimeinfo->subtype, mimeinfo->length);
421 if (mimeinfo->type != MIMETYPE_TEXT) {
422 gtk_text_buffer_insert(buffer, &iter, buf, -1);
423 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
424 if (prefs_common.display_header && (charcount > 0))
425 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
426 if (textview->messageview->forced_charset)
427 charset = textview->messageview->forced_charset;
428 else if (prefs_common.force_charset)
429 charset = prefs_common.force_charset;
431 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
433 textview_write_body(textview, mimeinfo, charset);
438 static gboolean add_parts_func(GNode *node, gpointer data)
440 MimeInfo *mimeinfo = (MimeInfo *) node->data;
441 TextView *textview = (TextView *) data;
443 g_return_val_if_fail(mimeinfo != NULL, FALSE);
445 textview_add_part(textview, mimeinfo);
450 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
452 g_return_if_fail(mimeinfo != NULL);
454 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, add_parts_func, textview);
458 static void recursive_add_parts(TextView *textview, GNode *node)
463 mimeinfo = (MimeInfo *) node->data;
465 textview_add_part(textview, mimeinfo);
467 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
468 (mimeinfo->type != MIMETYPE_MESSAGE))
471 if (strcasecmp(mimeinfo->subtype, "alternative") == 0) {
472 GNode * prefered_body;
480 prefered_body = NULL;
483 for(iter = g_node_first_child(node) ; iter != NULL ;
484 iter = g_node_next_sibling(iter)) {
489 submime = (MimeInfo *) iter->data;
490 if (submime->type == MIMETYPE_TEXT)
493 if (submime->subtype != NULL) {
494 if (strcasecmp(submime->subtype, "plain") == 0)
498 if (score > prefered_score) {
499 prefered_score = score;
500 prefered_body = iter;
504 if (prefered_body != NULL) {
505 recursive_add_parts(textview, prefered_body);
509 for(iter = g_node_first_child(node) ; iter != NULL ;
510 iter = g_node_next_sibling(iter)) {
511 recursive_add_parts(textview, iter);
516 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
518 g_return_if_fail(mimeinfo != NULL);
520 recursive_add_parts(textview, mimeinfo->node);
523 #define TEXT_INSERT(str) \
524 gtk_text_buffer_insert(buffer, &iter, str, -1)
526 void textview_show_error(TextView *textview)
529 GtkTextBuffer *buffer;
532 textview_set_font(textview, NULL);
533 textview_clear(textview);
535 text = GTK_TEXT_VIEW(textview->text);
536 buffer = gtk_text_view_get_buffer(text);
537 gtk_text_buffer_get_start_iter(buffer, &iter);
539 TEXT_INSERT(_("This message can't be displayed.\n"));
543 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
546 GtkTextBuffer *buffer;
549 if (!partinfo) return;
551 textview_set_font(textview, NULL);
552 textview_clear(textview);
554 text = GTK_TEXT_VIEW(textview->text);
555 buffer = gtk_text_view_get_buffer(text);
556 gtk_text_buffer_get_start_iter(buffer, &iter);
558 TEXT_INSERT(_("The following can be performed on this part by "));
559 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
561 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
562 TEXT_INSERT(_(" To display as text select 'Display as text' "));
563 TEXT_INSERT(_("(Shortcut key: 't')\n"));
564 TEXT_INSERT(_(" To open with an external program select 'Open' "));
565 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
566 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
567 TEXT_INSERT(_("mouse button),\n"));
568 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
574 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
575 const gchar *charset)
581 conv = conv_code_converter_new(charset);
583 textview->is_in_signature = FALSE;
585 if(mimeinfo->encoding_type != ENC_BINARY &&
586 mimeinfo->encoding_type != ENC_7BIT &&
587 mimeinfo->encoding_type != ENC_8BIT)
588 procmime_decode_content(mimeinfo);
590 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
593 filename = procmime_get_tmp_file_name(mimeinfo);
594 if (procmime_get_part(filename, mimeinfo) == 0) {
595 tmpfp = fopen(filename, "rb");
596 textview_show_html(textview, tmpfp, conv);
601 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
604 filename = procmime_get_tmp_file_name(mimeinfo);
605 if (procmime_get_part(filename, mimeinfo) == 0) {
606 tmpfp = fopen(filename, "rb");
607 textview_show_ertf(textview, tmpfp, conv);
613 tmpfp = fopen(mimeinfo->filename, "rb");
614 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
615 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
616 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
617 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
618 textview_write_line(textview, buf, conv);
622 conv_code_converter_destroy(conv);
625 static void textview_show_html(TextView *textview, FILE *fp,
631 parser = html_parser_new(fp, conv);
632 g_return_if_fail(parser != NULL);
634 while ((str = html_parse(parser)) != NULL) {
635 if (parser->state == HTML_HREF) {
636 /* first time : get and copy the URL */
637 if (parser->href == NULL) {
638 /* ALF - the sylpheed html parser returns an empty string,
639 * if still inside an <a>, but already parsed past HREF */
640 str = strtok(str, " ");
642 parser->href = strdup(str);
643 /* the URL may (or not) be followed by the
645 str = strtok(NULL, "");
649 textview_write_link(textview, str, parser->href, NULL);
651 textview_write_line(textview, str, NULL);
653 html_parser_destroy(parser);
656 static void textview_show_ertf(TextView *textview, FILE *fp,
662 parser = ertf_parser_new(fp, conv);
663 g_return_if_fail(parser != NULL);
665 while ((str = ertf_parse(parser)) != NULL) {
666 textview_write_line(textview, str, NULL);
669 ertf_parser_destroy(parser);
672 /* get_uri_part() - retrieves a URI starting from scanpos.
673 Returns TRUE if succesful */
674 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
675 const gchar **bp, const gchar **ep)
679 g_return_val_if_fail(start != NULL, FALSE);
680 g_return_val_if_fail(scanpos != NULL, FALSE);
681 g_return_val_if_fail(bp != NULL, FALSE);
682 g_return_val_if_fail(ep != NULL, FALSE);
686 /* find end point of URI */
687 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
688 if (!isgraph(*(const guchar *)ep_) ||
689 !isascii(*(const guchar *)ep_) ||
690 strchr("()<>\"", *ep_))
694 /* no punctuation at end of string */
696 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
697 * should pass some URI type to this function and decide on that whether
698 * to perform punctuation stripping */
700 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
702 for (; ep_ - 1 > scanpos + 1 &&
703 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
714 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
716 return g_strndup(bp, ep - bp);
719 /* valid mail address characters */
720 #define IS_RFC822_CHAR(ch) \
725 !strchr("(),;<>\"", (ch)))
727 /* alphabet and number within 7bit ASCII */
728 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
729 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
731 static GHashTable *create_domain_tab(void)
733 static const gchar *toplvl_domains [] = {
735 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
736 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
737 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
738 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
739 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
740 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
741 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
742 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
743 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
744 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
745 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
746 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
747 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
748 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
749 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
750 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
751 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
752 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
753 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
754 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
755 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
756 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
757 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
758 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
759 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
760 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
763 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
765 g_return_val_if_fail(htab, NULL);
766 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
767 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
771 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
773 const gint MAX_LVL_DOM_NAME_LEN = 6;
774 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
775 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
778 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
781 for (p = buf; p < m && first < last; *p++ = *first++)
785 return g_hash_table_lookup(tab, buf) != NULL;
788 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
789 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
790 const gchar **bp, const gchar **ep)
792 /* more complex than the uri part because we need to scan back and forward starting from
793 * the scan position. */
794 gboolean result = FALSE;
795 const gchar *bp_ = NULL;
796 const gchar *ep_ = NULL;
797 static GHashTable *dom_tab;
798 const gchar *last_dot = NULL;
799 const gchar *prelast_dot = NULL;
800 const gchar *last_tld_char = NULL;
802 /* the informative part of the email address (describing the name
803 * of the email address owner) may contain quoted parts. the
804 * closure stack stores the last encountered quotes. */
805 gchar closure_stack[128];
806 gchar *ptr = closure_stack;
808 g_return_val_if_fail(start != NULL, FALSE);
809 g_return_val_if_fail(scanpos != NULL, FALSE);
810 g_return_val_if_fail(bp != NULL, FALSE);
811 g_return_val_if_fail(ep != NULL, FALSE);
814 dom_tab = create_domain_tab();
815 g_return_val_if_fail(dom_tab, FALSE);
817 /* scan start of address */
818 for (bp_ = scanpos - 1;
819 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
822 /* TODO: should start with an alnum? */
824 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
827 if (bp_ != scanpos) {
828 /* scan end of address */
829 for (ep_ = scanpos + 1;
830 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
832 prelast_dot = last_dot;
834 if (*(last_dot + 1) == '.') {
835 if (prelast_dot == NULL)
837 last_dot = prelast_dot;
842 /* TODO: really should terminate with an alnum? */
843 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
848 if (last_dot == NULL)
851 last_dot = prelast_dot;
852 if (last_dot == NULL || (scanpos + 1 >= last_dot))
856 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
857 if (*last_tld_char == '?')
860 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
867 if (!result) return FALSE;
869 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
870 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
873 /* see if this is <bracketed>; in this case we also scan for the informative part. */
874 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
877 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
878 #define IN_STACK() (ptr > closure_stack)
879 /* has underrun check */
880 #define POP_STACK() if(IN_STACK()) --ptr
881 /* has overrun check */
882 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
883 /* has underrun check */
884 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
888 /* scan for the informative part. */
889 for (bp_ -= 2; bp_ >= start; bp_--) {
890 /* if closure on the stack keep scanning */
891 if (PEEK_STACK() == *bp_) {
895 if (*bp_ == '\'' || *bp_ == '"') {
900 /* if nothing in the closure stack, do the special conditions
901 * the following if..else expression simply checks whether
902 * a token is acceptable. if not acceptable, the clause
903 * should terminate the loop with a 'break' */
906 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
907 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
908 /* hyphens are allowed, but only in
910 } else if (!ispunct(*bp_)) {
911 /* but anything not being a punctiation
914 break; /* anything else is rejected */
927 /* scan forward (should start with an alnum) */
928 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
938 #undef IS_ASCII_ALNUM
939 #undef IS_RFC822_CHAR
941 static gchar *make_email_string(const gchar *bp, const gchar *ep)
943 /* returns a mailto: URI; mailto: is also used to detect the
944 * uri type later on in the button_pressed signal handler */
948 tmp = g_strndup(bp, ep - bp);
949 result = g_strconcat("mailto:", tmp, NULL);
955 static gchar *make_http_string(const gchar *bp, const gchar *ep)
957 /* returns an http: URI; */
961 tmp = g_strndup(bp, ep - bp);
962 result = g_strconcat("http://", tmp, NULL);
968 #define ADD_TXT_POS(bp_, ep_, pti_) \
969 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
971 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
974 g_warning("alloc error scanning URIs\n"); \
975 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
981 /* textview_make_clickable_parts() - colorizes clickable parts */
982 static void textview_make_clickable_parts(TextView *textview,
984 const gchar *uri_tag,
985 const gchar *linebuf)
987 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
988 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
991 /* parse table - in order of priority */
993 const gchar *needle; /* token */
995 /* token search function */
996 gchar *(*search) (const gchar *haystack,
997 const gchar *needle);
998 /* part parsing function */
999 gboolean (*parse) (const gchar *start,
1000 const gchar *scanpos,
1003 /* part to URI function */
1004 gchar *(*build_uri) (const gchar *bp,
1008 static struct table parser[] = {
1009 {"http://", strcasestr, get_uri_part, make_uri_string},
1010 {"https://", strcasestr, get_uri_part, make_uri_string},
1011 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1012 {"www.", strcasestr, get_uri_part, make_http_string},
1013 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1014 {"@", strcasestr, get_email_part, make_email_string}
1016 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1019 const gchar *walk, *bp, *ep;
1022 const gchar *bp, *ep; /* text position */
1023 gint pti; /* index in parse table */
1024 struct txtpos *next; /* next */
1025 } head = {NULL, NULL, 0, NULL}, *last = &head;
1027 gtk_text_buffer_get_end_iter(buffer, &iter);
1029 /* parse for clickable parts, and build a list of begin and end positions */
1030 for (walk = linebuf, n = 0;;) {
1031 gint last_index = PARSE_ELEMS;
1032 gchar *scanpos = NULL;
1034 /* FIXME: this looks phony. scanning for anything in the parse table */
1035 for (n = 0; n < PARSE_ELEMS; n++) {
1038 tmp = parser[n].search(walk, parser[n].needle);
1040 if (scanpos == NULL || tmp < scanpos) {
1048 /* check if URI can be parsed */
1049 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1050 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1051 ADD_TXT_POS(bp, ep, last_index);
1055 strlen(parser[last_index].needle);
1060 /* colorize this line */
1062 const gchar *normal_text = linebuf;
1065 for (last = head.next; last != NULL;
1066 normal_text = last->ep, last = last->next) {
1069 uri = g_new(RemoteURI, 1);
1070 if (last->bp - normal_text > 0)
1071 gtk_text_buffer_insert_with_tags_by_name
1074 last->bp - normal_text,
1076 uri->uri = parser[last->pti].build_uri(last->bp,
1078 uri->start = gtk_text_iter_get_offset(&iter);
1079 gtk_text_buffer_insert_with_tags_by_name
1081 last->bp, last->ep - last->bp,
1083 uri->end = gtk_text_iter_get_offset(&iter);
1084 textview->uri_list =
1085 g_slist_append(textview->uri_list, uri);
1089 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1093 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1101 static void textview_write_line(TextView *textview, const gchar *str,
1102 CodeConverter *conv)
1105 GtkTextBuffer *buffer;
1107 gchar buf[BUFFSIZE];
1109 gint quotelevel = -1;
1110 gchar quote_tag_str[10];
1112 text = GTK_TEXT_VIEW(textview->text);
1113 buffer = gtk_text_view_get_buffer(text);
1114 gtk_text_buffer_get_end_iter(buffer, &iter);
1117 strncpy2(buf, str, sizeof(buf));
1118 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1119 strncpy2(buf, str, sizeof(buf));
1122 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1125 /* change color of quotation
1126 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1127 Up to 3 levels of quotations are detected, and each
1128 level is colored using a different color. */
1129 if (prefs_common.enable_color
1130 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1131 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1133 /* set up the correct foreground color */
1134 if (quotelevel > 2) {
1135 /* recycle colors */
1136 if (prefs_common.recycle_quote_colors)
1143 if (quotelevel == -1)
1146 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1147 "quote%d", quotelevel);
1148 fg_color = quote_tag_str;
1151 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1152 fg_color = "signature";
1153 textview->is_in_signature = TRUE;
1156 if (prefs_common.enable_color)
1157 textview_make_clickable_parts(textview, fg_color, "link", buf);
1159 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1162 void textview_write_link(TextView *textview, const gchar *str,
1163 const gchar *uri, CodeConverter *conv)
1165 GdkColor *link_color = NULL;
1167 GtkTextBuffer *buffer;
1169 gchar buf[BUFFSIZE];
1176 text = GTK_TEXT_VIEW(textview->text);
1177 buffer = gtk_text_view_get_buffer(text);
1178 gtk_text_buffer_get_end_iter(buffer, &iter);
1183 if (textview->text_is_mb)
1184 conv_localetodisp(buf, sizeof(buf), str);
1186 strncpy2(buf, str, sizeof(buf));
1187 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1188 conv_localetodisp(buf, sizeof(buf), str);
1189 else if (textview->text_is_mb)
1190 conv_unreadable_locale(buf);
1193 strncpy2(buf, str, sizeof(buf));
1194 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1195 conv_localetodisp(buf, sizeof(buf), str);
1200 gtk_text_buffer_get_end_iter(buffer, &iter);
1202 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1203 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1205 if (prefs_common.enable_color) {
1206 link_color = &uri_color;
1208 r_uri = g_new(RemoteURI, 1);
1209 r_uri->uri = g_strdup(uri);
1210 r_uri->start = gtk_text_iter_get_offset(&iter);
1211 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1213 r_uri->end = gtk_text_iter_get_offset(&iter);
1214 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1217 void textview_clear(TextView *textview)
1219 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1220 GtkTextBuffer *buffer;
1222 buffer = gtk_text_view_get_buffer(text);
1223 gtk_text_buffer_set_text(buffer, "\0", -1);
1225 TEXTVIEW_STATUSBAR_POP(textview);
1226 textview_uri_list_remove_all(textview->uri_list);
1227 textview->uri_list = NULL;
1229 textview->body_pos = 0;
1232 void textview_destroy(TextView *textview)
1234 textview_uri_list_remove_all(textview->uri_list);
1235 textview->uri_list = NULL;
1240 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1242 textview->show_all_headers = all_headers;
1245 void textview_set_font(TextView *textview, const gchar *codeset)
1247 if (prefs_common.textfont) {
1248 PangoFontDescription *font_desc = NULL;
1250 if (prefs_common.textfont)
1251 font_desc = pango_font_description_from_string
1252 (prefs_common.textfont);
1254 gtk_widget_modify_font(textview->text, font_desc);
1255 pango_font_description_free(font_desc);
1258 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1259 prefs_common.line_space / 2);
1260 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1261 prefs_common.line_space / 2);
1262 if (prefs_common.head_space) {
1263 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1265 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1269 void textview_set_text(TextView *textview, const gchar *text)
1272 GtkTextBuffer *buffer;
1274 g_return_if_fail(textview != NULL);
1275 g_return_if_fail(text != NULL);
1277 textview_clear(textview);
1279 view = GTK_TEXT_VIEW(textview->text);
1280 buffer = gtk_text_view_get_buffer(view);
1281 gtk_text_buffer_set_text(buffer, text, strlen(text));
1297 H_ORGANIZATION = 11,
1300 void textview_set_position(TextView *textview, gint pos)
1302 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1303 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1306 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1307 gtk_text_buffer_place_cursor(buffer, &iter);
1308 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1311 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1313 gchar buf[BUFFSIZE];
1314 GPtrArray *headers, *sorted_headers;
1315 GSList *disphdr_list;
1319 g_return_val_if_fail(fp != NULL, NULL);
1321 if (textview->show_all_headers)
1322 return procheader_get_header_array_asis(fp);
1324 if (!prefs_common.display_header) {
1325 while (fgets(buf, sizeof(buf), fp) != NULL)
1326 if (buf[0] == '\r' || buf[0] == '\n') break;
1330 headers = procheader_get_header_array_asis(fp);
1332 sorted_headers = g_ptr_array_new();
1334 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1335 disphdr_list = disphdr_list->next) {
1336 DisplayHeaderProp *dp =
1337 (DisplayHeaderProp *)disphdr_list->data;
1339 for (i = 0; i < headers->len; i++) {
1340 header = g_ptr_array_index(headers, i);
1342 if (procheader_headername_equal(header->name,
1345 procheader_header_free(header);
1347 g_ptr_array_add(sorted_headers, header);
1349 g_ptr_array_remove_index(headers, i);
1355 if (prefs_common.show_other_header) {
1356 for (i = 0; i < headers->len; i++) {
1357 header = g_ptr_array_index(headers, i);
1358 g_ptr_array_add(sorted_headers, header);
1360 g_ptr_array_free(headers, TRUE);
1362 procheader_header_array_destroy(headers);
1365 return sorted_headers;
1368 static void textview_show_header(TextView *textview, GPtrArray *headers)
1370 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1371 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1376 g_return_if_fail(headers != NULL);
1378 for (i = 0; i < headers->len; i++) {
1379 header = g_ptr_array_index(headers, i);
1380 g_return_if_fail(header->name != NULL);
1382 gtk_text_buffer_get_end_iter (buffer, &iter);
1383 gtk_text_buffer_insert_with_tags_by_name
1384 (buffer, &iter, header->name, -1,
1385 "header_title", "header", NULL);
1386 if (header->name[strlen(header->name) - 1] != ' ')
1387 gtk_text_buffer_insert_with_tags_by_name
1388 (buffer, &iter, " ", 1,
1389 "header_title", "header", NULL);
1391 if (procheader_headername_equal(header->name, "Subject") ||
1392 procheader_headername_equal(header->name, "From") ||
1393 procheader_headername_equal(header->name, "To") ||
1394 procheader_headername_equal(header->name, "Cc"))
1395 unfold_line(header->body);
1399 if (textview->text_is_mb == TRUE)
1400 conv_unreadable_locale(header->body);
1403 if (prefs_common.enable_color &&
1404 (procheader_headername_equal(header->name, "X-Mailer") ||
1405 procheader_headername_equal(header->name,
1407 strstr(header->body, "Sylpheed") != NULL) {
1408 gtk_text_buffer_get_end_iter (buffer, &iter);
1409 gtk_text_buffer_insert_with_tags_by_name
1410 (buffer, &iter, header->body, -1,
1411 "header", "emphasis", NULL);
1412 } else if (prefs_common.enable_color) {
1413 textview_make_clickable_parts(textview, "header", "link",
1416 textview_make_clickable_parts(textview, "header", NULL,
1419 gtk_text_buffer_get_end_iter (buffer, &iter);
1420 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1425 gboolean textview_search_string(TextView *textview, const gchar *str,
1428 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1429 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1431 GtkTextIter iter, start, end, real_end, *pos;
1432 gboolean found = FALSE;
1433 gint insert_offset, selbound_offset;
1435 /* reset selection */
1436 mark = gtk_text_buffer_get_mark(buffer, "insert");
1437 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1438 insert_offset = gtk_text_iter_get_offset(&start);
1439 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1440 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1441 selbound_offset = gtk_text_iter_get_offset(&end);
1443 pos = insert_offset > selbound_offset ? &start : &end;
1444 gtk_text_buffer_place_cursor(buffer, pos);
1447 mark = gtk_text_buffer_get_insert(buffer);
1448 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1450 found = gtk_text_iter_forward_search(&iter, str,
1451 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1452 &start, &end, NULL);
1456 gtk_text_buffer_get_end_iter(buffer, &real_end);
1457 text = strdup(gtk_text_buffer_get_text(buffer, &iter,
1460 while (!found && i++ < strlen(text) - 1) {
1461 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1464 i += gtk_text_iter_get_offset(&end);
1467 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1468 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1476 gtk_text_buffer_place_cursor(buffer, &start);
1477 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1479 mark = gtk_text_buffer_get_mark(buffer, "insert");
1480 gtk_text_view_scroll_mark_onscreen(text, mark);
1486 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1489 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1490 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1492 GtkTextIter iter, start, real_start, end, *pos;
1493 gboolean found = FALSE;
1494 gint insert_offset, selbound_offset;
1496 /* reset selection */
1497 mark = gtk_text_buffer_get_mark(buffer, "insert");
1498 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1499 insert_offset = gtk_text_iter_get_offset(&start);
1500 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1501 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1502 selbound_offset = gtk_text_iter_get_offset(&end);
1504 pos = insert_offset < selbound_offset ? &start : &end;
1505 gtk_text_buffer_place_cursor(buffer, pos);
1508 mark = gtk_text_buffer_get_insert(buffer);
1509 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1511 found = gtk_text_iter_backward_search(&iter, str,
1512 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1513 &start, &end, NULL);
1517 if (gtk_text_iter_get_offset(&iter) == 0)
1518 gtk_text_buffer_get_end_iter(buffer, &iter);
1520 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1521 gtk_text_buffer_get_start_iter(buffer, &real_start);
1523 text = strdup(gtk_text_buffer_get_text(buffer, &real_start,
1526 while (!found && i-- > 0) {
1527 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1531 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1532 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1540 gtk_text_buffer_place_cursor(buffer, &end);
1541 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1543 mark = gtk_text_buffer_get_mark(buffer, "insert");
1544 gtk_text_view_scroll_mark_onscreen(text, mark);
1550 void textview_scroll_one_line(TextView *textview, gboolean up)
1552 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1553 GtkAdjustment *vadj = text->vadjustment;
1556 if (prefs_common.enable_smooth_scroll) {
1557 textview_smooth_scroll_one_line(textview, up);
1562 upper = vadj->upper - vadj->page_size;
1563 if (vadj->value < upper) {
1565 vadj->step_increment * 4;
1567 MIN(vadj->value, upper);
1568 g_signal_emit_by_name(G_OBJECT(vadj),
1569 "value_changed", 0);
1572 if (vadj->value > 0.0) {
1574 vadj->step_increment * 4;
1576 MAX(vadj->value, 0.0);
1577 g_signal_emit_by_name(G_OBJECT(vadj),
1578 "value_changed", 0);
1583 gboolean textview_scroll_page(TextView *textview, gboolean up)
1585 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1586 GtkAdjustment *vadj = text->vadjustment;
1590 if (prefs_common.enable_smooth_scroll)
1591 return textview_smooth_scroll_page(textview, up);
1593 if (prefs_common.scroll_halfpage)
1594 page_incr = vadj->page_increment / 2;
1596 page_incr = vadj->page_increment;
1599 upper = vadj->upper - vadj->page_size;
1600 if (vadj->value < upper) {
1601 vadj->value += page_incr;
1602 vadj->value = MIN(vadj->value, upper);
1603 g_signal_emit_by_name(G_OBJECT(vadj),
1604 "value_changed", 0);
1608 if (vadj->value > 0.0) {
1609 vadj->value -= page_incr;
1610 vadj->value = MAX(vadj->value, 0.0);
1611 g_signal_emit_by_name(G_OBJECT(vadj),
1612 "value_changed", 0);
1620 static void textview_smooth_scroll_do(TextView *textview,
1621 gfloat old_value, gfloat last_value,
1624 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1625 GtkAdjustment *vadj = text->vadjustment;
1630 if (old_value < last_value) {
1631 change_value = last_value - old_value;
1634 change_value = old_value - last_value;
1638 for (i = step; i <= change_value; i += step) {
1639 vadj->value = old_value + (up ? -i : i);
1640 g_signal_emit_by_name(G_OBJECT(vadj),
1641 "value_changed", 0);
1644 vadj->value = last_value;
1645 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1648 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1650 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1651 GtkAdjustment *vadj = text->vadjustment;
1657 upper = vadj->upper - vadj->page_size;
1658 if (vadj->value < upper) {
1659 old_value = vadj->value;
1660 last_value = vadj->value +
1661 vadj->step_increment * 4;
1662 last_value = MIN(last_value, upper);
1664 textview_smooth_scroll_do(textview, old_value,
1666 prefs_common.scroll_step);
1669 if (vadj->value > 0.0) {
1670 old_value = vadj->value;
1671 last_value = vadj->value -
1672 vadj->step_increment * 4;
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(textview, FALSE);
1758 textview_scroll_page(textview, TRUE);
1761 textview_scroll_one_line(textview,
1762 (event->state & GDK_MOD1_MASK) != 0);
1766 summary_pass_key_press_event(summaryview, event);
1771 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1772 KEY_PRESS_EVENT_STOP();
1773 mimeview_pass_key_press_event(messageview->mimeview,
1777 /* possible fall through */
1780 event->window != messageview->mainwin->window->window) {
1781 GdkEventKey tmpev = *event;
1783 tmpev.window = messageview->mainwin->window->window;
1784 KEY_PRESS_EVENT_STOP();
1785 gtk_widget_event(messageview->mainwin->window,
1786 (GdkEvent *)&tmpev);
1794 static gint show_url_timeout_cb(gpointer data)
1796 TextView *textview = (TextView *)data;
1798 TEXTVIEW_STATUSBAR_POP(textview);
1799 textview->show_url_timeout_tag = 0;
1804 *\brief Check to see if a web URL has been disguised as a different
1805 * URL (possible with HTML email).
1807 *\param uri The uri to check
1809 *\param textview The TextView the URL is contained in
1811 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1812 * it anyway, otherwise FALSE
1814 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1817 gboolean retval = TRUE;
1819 if (g_strncasecmp(uri->uri, "http:", 5) &&
1820 g_strncasecmp(uri->uri, "https:", 6) &&
1821 g_strncasecmp(uri->uri, "www.", 4))
1824 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1827 if (clicked_str == NULL)
1830 if (strcmp(clicked_str, uri->uri) &&
1831 (!g_strncasecmp(clicked_str, "http:", 5) ||
1832 !g_strncasecmp(clicked_str, "https:", 6) ||
1833 !g_strncasecmp(clicked_str, "www.", 4))) {
1837 /* allow uri->uri == http://somewhere.com
1838 and clicked_str == somewhere.com */
1839 str = g_strconcat("http://", clicked_str, NULL);
1841 if (!g_strcasecmp(str, uri->uri))
1846 if (retval == FALSE) {
1850 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1851 "the apparent URL (%s). \n"
1853 uri->uri, clicked_str);
1854 resp = alertpanel_with_type(_("Warning"),
1858 NULL, NULL, ALERT_WARNING);
1860 if (resp == G_ALERTDEFAULT)
1863 g_free(clicked_str);
1867 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1868 GdkEvent *event, GtkTextIter *iter,
1871 GtkTextIter start_iter, end_iter;
1872 gint start_pos, end_pos;
1873 GdkEventButton *bevent;
1876 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
1877 && event->type != GDK_MOTION_NOTIFY)
1880 bevent = (GdkEventButton *) event;
1882 /* get start and end positions */
1884 if(!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) {
1885 debug_print("Can't find start.");
1888 start_pos = gtk_text_iter_get_offset(&start_iter);
1891 if(!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) {
1892 debug_print("Can't find end");
1895 end_pos = gtk_text_iter_get_offset(&end_iter);
1897 /* search current uri */
1898 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1899 RemoteURI *uri = (RemoteURI *)cur->data;
1901 if (start_pos != uri->start || end_pos != uri->end)
1904 trimmed_uri = trim_string(uri->uri, 60);
1905 /* hover or single click: display url in statusbar */
1907 if (event->type == GDK_MOTION_NOTIFY
1908 || (event->type == GDK_BUTTON_PRESS && bevent->button == 1)) {
1909 if (textview->messageview->mainwin) {
1910 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1911 textview->show_url_timeout_tag = gtk_timeout_add
1912 (4000, show_url_timeout_cb, textview);
1916 /* doubleclick: open compose / add address / browser */
1917 if ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) ||
1918 bevent->button == 2 || bevent->button == 3) {
1919 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1920 if (bevent->button == 3) {
1921 gchar *fromname, *fromaddress;
1924 fromaddress = g_strdup(uri->uri + 7);
1925 /* Hiroyuki: please put this function in utils.c! */
1926 fromname = procheader_get_fromname(fromaddress);
1927 extract_address(fromaddress);
1928 g_message("adding from textview %s <%s>", fromname, fromaddress);
1929 /* Add to address book - Match */
1930 addressbook_add_contact( fromname, fromaddress, NULL );
1932 g_free(fromaddress);
1935 PrefsAccount *account = NULL;
1937 if (textview->messageview && textview->messageview->msginfo &&
1938 textview->messageview->msginfo->folder) {
1939 FolderItem *folder_item;
1941 folder_item = textview->messageview->msginfo->folder;
1942 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1943 account = account_find_from_id(folder_item->prefs->default_account);
1945 compose_new(account, uri->uri + 7, NULL);
1949 if (textview_uri_security_check(textview, uri) == TRUE)
1951 prefs_common.uri_cmd);
1955 g_free(trimmed_uri);
1962 *\brief Check to see if a web URL has been disguised as a different
1963 * URL (possible with HTML email).
1965 *\param uri The uri to check
1967 *\param textview The TextView the URL is contained in
1969 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1970 * it anyway, otherwise FALSE
1972 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
1975 gboolean retval = TRUE;
1977 if (is_uri_string(uri->uri) == FALSE)
1980 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1981 uri->start, uri->end);
1982 if (visible_str == NULL)
1985 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
1987 gchar *visible_uri_path;
1989 uri_path = get_uri_path(uri->uri);
1990 visible_uri_path = get_uri_path(visible_str);
1991 if (strcmp(uri_path, visible_uri_path) != 0)
1995 if (retval == FALSE) {
1999 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2000 "the apparent URL (%s).\n"
2002 uri->uri, visible_str);
2003 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"), _("No"), NULL,
2004 NULL, ALERT_WARNING);
2006 if (aval == G_ALERTDEFAULT)
2010 g_free(visible_str);
2015 static void textview_uri_list_remove_all(GSList *uri_list)
2019 for (cur = uri_list; cur != NULL; cur = cur->next) {
2021 g_free(((RemoteURI *)cur->data)->uri);
2026 g_slist_free(uri_list);