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 static void textview_show_html (TextView *textview,
125 CodeConverter *conv);
127 static void textview_write_line (TextView *textview,
129 CodeConverter *conv);
130 static void textview_write_link (TextView *textview,
133 CodeConverter *conv);
135 static GPtrArray *textview_scan_header (TextView *textview,
137 static void textview_show_header (TextView *textview,
140 static gint textview_key_pressed (GtkWidget *widget,
143 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
144 GdkEvent *event, GtkTextIter *iter,
146 static void textview_smooth_scroll_do (TextView *textview,
150 static void textview_smooth_scroll_one_line (TextView *textview,
152 static gboolean textview_smooth_scroll_page (TextView *textview,
155 static gboolean textview_uri_security_check (TextView *textview,
157 static void textview_uri_list_remove_all (GSList *uri_list);
160 static void populate_popup(GtkTextView *textview, GtkMenu *menu,
163 gtk_menu_detach(menu);
166 TextView *textview_create(void)
170 GtkWidget *scrolledwin;
172 GtkTextBuffer *buffer;
173 GtkClipboard *clipboard;
174 PangoFontDescription *font_desc = NULL;
176 debug_print("Creating text view...\n");
177 textview = g_new0(TextView, 1);
179 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
180 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
181 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
182 gtk_widget_set_size_request(scrolledwin, prefs_common.mainview_width, -1);
183 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
186 /* create GtkSText widgets for single-byte and multi-byte character */
187 text = gtk_text_view_new();
188 gtk_widget_show(text);
189 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
190 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
191 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
192 g_signal_connect(G_OBJECT(text), "populate-popup",
193 G_CALLBACK(populate_popup), NULL);
196 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
197 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
198 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
200 gtk_widget_ensure_style(text);
202 if (prefs_common.normalfont)
203 font_desc = pango_font_description_from_string
204 (prefs_common.normalfont);
206 gtk_widget_modify_font(text, font_desc);
208 pango_font_description_free(font_desc);
210 gtk_widget_ref(scrolledwin);
212 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
214 g_signal_connect(G_OBJECT(text), "key_press_event",
215 G_CALLBACK(textview_key_pressed),
218 gtk_widget_show(scrolledwin);
220 vbox = gtk_vbox_new(FALSE, 0);
221 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
223 gtk_widget_show(vbox);
225 textview->vbox = vbox;
226 textview->scrolledwin = scrolledwin;
227 textview->text = text;
228 textview->uri_list = NULL;
229 textview->body_pos = 0;
230 textview->show_all_headers = FALSE;
231 textview->last_buttonpress = GDK_NOTHING;
232 textview->show_url_msgid = 0;
237 static void textview_create_tags(GtkTextView *text, TextView *textview)
239 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
242 gtk_text_buffer_create_tag(buffer, "header",
243 "pixels-above-lines", 0,
244 "pixels-above-lines-set", TRUE,
245 "pixels-below-lines", 0,
246 "pixels-below-lines-set", TRUE,
248 "left-margin-set", TRUE,
250 gtk_text_buffer_create_tag(buffer, "header_title",
251 "font", prefs_common.boldfont,
253 gtk_text_buffer_create_tag(buffer, "quote0",
254 "foreground-gdk", "e_colors[0],
256 gtk_text_buffer_create_tag(buffer, "quote1",
257 "foreground-gdk", "e_colors[1],
259 gtk_text_buffer_create_tag(buffer, "quote2",
260 "foreground-gdk", "e_colors[2],
262 gtk_text_buffer_create_tag(buffer, "emphasis",
263 "foreground-gdk", &emphasis_color,
265 gtk_text_buffer_create_tag(buffer, "signature",
266 "foreground-gdk", &signature_color,
268 tag = gtk_text_buffer_create_tag(buffer, "link",
269 "foreground-gdk", &uri_color,
272 g_signal_connect(G_OBJECT(tag), "event",
273 G_CALLBACK(textview_uri_button_pressed), textview);
276 void textview_init(TextView *textview)
278 gtkut_widget_disable_theme_engine(textview->text);
279 textview_update_message_colors();
280 textview_set_all_headers(textview, FALSE);
281 textview_set_font(textview, NULL);
283 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
286 void textview_update_message_colors(void)
288 GdkColor black = {0, 0, 0, 0};
290 if (prefs_common.enable_color) {
291 /* grab the quote colors, converting from an int to a GdkColor */
292 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
294 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
296 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
298 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
300 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
303 quote_colors[0] = quote_colors[1] = quote_colors[2] =
304 uri_color = emphasis_color = signature_color = black;
308 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
313 if ((fp = fopen(file, "rb")) == NULL) {
314 FILE_OP_ERROR(file, "fopen");
318 textview_clear(textview);
320 textview_add_parts(textview, mimeinfo);
324 textview_set_position(textview, 0);
327 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
331 g_return_if_fail(mimeinfo != NULL);
332 g_return_if_fail(fp != NULL);
334 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
335 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
336 textview_clear(textview);
337 textview_add_parts(textview, mimeinfo);
341 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
344 textview_clear(textview);
346 if (mimeinfo->type == MIMETYPE_MULTIPART)
347 textview_add_parts(textview, mimeinfo);
349 textview_write_body(textview, mimeinfo);
353 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
356 GtkTextBuffer *buffer;
359 GPtrArray *headers = NULL;
364 g_return_if_fail(mimeinfo != NULL);
365 text = GTK_TEXT_VIEW(textview->text);
366 buffer = gtk_text_view_get_buffer(text);
367 charcount = gtk_text_buffer_get_char_count(buffer);
368 gtk_text_buffer_get_end_iter(buffer, &iter);
370 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
372 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
375 fp = fopen(mimeinfo->data.filename, "rb");
376 fseek(fp, mimeinfo->offset, SEEK_SET);
377 headers = textview_scan_header(textview, fp);
380 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
381 textview_show_header(textview, headers);
382 procheader_header_array_destroy(headers);
388 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
389 content_type = procmime_get_content_type_str(mimeinfo->type,
392 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
394 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
395 name, content_type, mimeinfo->length);
397 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
398 content_type, mimeinfo->length);
400 g_free(content_type);
402 if (mimeinfo->type != MIMETYPE_TEXT) {
403 gtk_text_buffer_insert(buffer, &iter, buf, -1);
404 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
405 if (prefs_common.display_header && (charcount > 0))
406 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
408 textview_write_body(textview, mimeinfo);
412 static void recursive_add_parts(TextView *textview, GNode *node)
417 mimeinfo = (MimeInfo *) node->data;
419 textview_add_part(textview, mimeinfo);
421 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
422 (mimeinfo->type != MIMETYPE_MESSAGE))
425 if (g_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
426 GNode * prefered_body;
434 prefered_body = NULL;
437 for(iter = g_node_first_child(node) ; iter != NULL ;
438 iter = g_node_next_sibling(iter)) {
443 submime = (MimeInfo *) iter->data;
444 if (submime->type == MIMETYPE_TEXT)
447 if (submime->subtype != NULL) {
448 if (g_strcasecmp(submime->subtype, "plain") == 0)
452 if (score > prefered_score) {
453 prefered_score = score;
454 prefered_body = iter;
458 if (prefered_body != NULL) {
459 recursive_add_parts(textview, prefered_body);
463 for(iter = g_node_first_child(node) ; iter != NULL ;
464 iter = g_node_next_sibling(iter)) {
465 recursive_add_parts(textview, iter);
470 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
472 g_return_if_fail(mimeinfo != NULL);
474 recursive_add_parts(textview, mimeinfo->node);
477 #define TEXT_INSERT(str) \
478 gtk_text_buffer_insert(buffer, &iter, str, -1)
480 void textview_show_error(TextView *textview)
483 GtkTextBuffer *buffer;
486 textview_set_font(textview, NULL);
487 textview_clear(textview);
489 text = GTK_TEXT_VIEW(textview->text);
490 buffer = gtk_text_view_get_buffer(text);
491 gtk_text_buffer_get_start_iter(buffer, &iter);
493 TEXT_INSERT(_("This message can't be displayed.\n"));
497 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
500 GtkTextBuffer *buffer;
503 if (!partinfo) return;
505 textview_set_font(textview, NULL);
506 textview_clear(textview);
508 text = GTK_TEXT_VIEW(textview->text);
509 buffer = gtk_text_view_get_buffer(text);
510 gtk_text_buffer_get_start_iter(buffer, &iter);
512 TEXT_INSERT(_("The following can be performed on this part by "));
513 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
515 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
516 TEXT_INSERT(_(" To display as text select 'Display as text' "));
517 TEXT_INSERT(_("(Shortcut key: 't')\n"));
518 TEXT_INSERT(_(" To open with an external program select 'Open' "));
519 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
520 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
521 TEXT_INSERT(_("mouse button),\n"));
522 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
528 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
533 const gchar *charset;
535 if (textview->messageview->forced_charset)
536 charset = textview->messageview->forced_charset;
538 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
540 textview_set_font(textview, charset);
542 conv = conv_code_converter_new(charset);
544 procmime_force_encoding(textview->messageview->forced_encoding);
546 textview->is_in_signature = FALSE;
548 procmime_decode_content(mimeinfo);
550 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
553 filename = procmime_get_tmp_file_name(mimeinfo);
554 if (procmime_get_part(filename, mimeinfo) == 0) {
555 tmpfp = fopen(filename, "rb");
556 textview_show_html(textview, tmpfp, conv);
561 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
564 filename = procmime_get_tmp_file_name(mimeinfo);
565 if (procmime_get_part(filename, mimeinfo) == 0) {
566 tmpfp = fopen(filename, "rb");
567 textview_show_ertf(textview, tmpfp, conv);
573 tmpfp = fopen(mimeinfo->data.filename, "rb");
574 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
575 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
576 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
577 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
578 textview_write_line(textview, buf, conv);
582 conv_code_converter_destroy(conv);
583 procmime_force_encoding(0);
586 static void textview_show_html(TextView *textview, FILE *fp,
592 parser = html_parser_new(fp, conv);
593 g_return_if_fail(parser != NULL);
595 while ((str = html_parse(parser)) != NULL) {
596 if (parser->state == HTML_HREF) {
597 /* first time : get and copy the URL */
598 if (parser->href == NULL) {
599 /* ALF - the sylpheed html parser returns an empty string,
600 * if still inside an <a>, but already parsed past HREF */
601 str = strtok(str, " ");
603 parser->href = g_strdup(str);
604 /* the URL may (or not) be followed by the
606 str = strtok(NULL, "");
610 textview_write_link(textview, str, parser->href, NULL);
612 textview_write_line(textview, str, NULL);
614 html_parser_destroy(parser);
617 static void textview_show_ertf(TextView *textview, FILE *fp,
623 parser = ertf_parser_new(fp, conv);
624 g_return_if_fail(parser != NULL);
626 while ((str = ertf_parse(parser)) != NULL) {
627 textview_write_line(textview, str, NULL);
630 ertf_parser_destroy(parser);
633 /* get_uri_part() - retrieves a URI starting from scanpos.
634 Returns TRUE if succesful */
635 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
636 const gchar **bp, const gchar **ep)
640 g_return_val_if_fail(start != NULL, FALSE);
641 g_return_val_if_fail(scanpos != NULL, FALSE);
642 g_return_val_if_fail(bp != NULL, FALSE);
643 g_return_val_if_fail(ep != NULL, FALSE);
647 /* find end point of URI */
648 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
649 if (!isgraph(*(const guchar *)ep_) ||
650 !IS_ASCII(*(const guchar *)ep_) ||
651 strchr("()<>\"", *ep_))
655 /* no punctuation at end of string */
657 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
658 * should pass some URI type to this function and decide on that whether
659 * to perform punctuation stripping */
661 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
663 for (; ep_ - 1 > scanpos + 1 &&
664 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
675 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
677 return g_strndup(bp, ep - bp);
680 /* valid mail address characters */
681 #define IS_RFC822_CHAR(ch) \
686 !strchr("(),;<>\"", (ch)))
688 /* alphabet and number within 7bit ASCII */
689 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && isalnum(ch))
690 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
692 static GHashTable *create_domain_tab(void)
694 static const gchar *toplvl_domains [] = {
696 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
697 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
698 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
699 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
700 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
701 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
702 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
703 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
704 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
705 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
706 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
707 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
708 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
709 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
710 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
711 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
712 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
713 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
714 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
715 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
716 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
717 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
718 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
719 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
720 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
721 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
724 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
726 g_return_val_if_fail(htab, NULL);
727 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
728 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
732 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
734 const gint MAX_LVL_DOM_NAME_LEN = 6;
735 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
736 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
739 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
742 for (p = buf; p < m && first < last; *p++ = *first++)
746 return g_hash_table_lookup(tab, buf) != NULL;
749 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
750 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
751 const gchar **bp, const gchar **ep)
753 /* more complex than the uri part because we need to scan back and forward starting from
754 * the scan position. */
755 gboolean result = FALSE;
756 const gchar *bp_ = NULL;
757 const gchar *ep_ = NULL;
758 static GHashTable *dom_tab;
759 const gchar *last_dot = NULL;
760 const gchar *prelast_dot = NULL;
761 const gchar *last_tld_char = NULL;
763 /* the informative part of the email address (describing the name
764 * of the email address owner) may contain quoted parts. the
765 * closure stack stores the last encountered quotes. */
766 gchar closure_stack[128];
767 gchar *ptr = closure_stack;
769 g_return_val_if_fail(start != NULL, FALSE);
770 g_return_val_if_fail(scanpos != NULL, FALSE);
771 g_return_val_if_fail(bp != NULL, FALSE);
772 g_return_val_if_fail(ep != NULL, FALSE);
775 dom_tab = create_domain_tab();
776 g_return_val_if_fail(dom_tab, FALSE);
778 /* scan start of address */
779 for (bp_ = scanpos - 1;
780 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
783 /* TODO: should start with an alnum? */
785 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
788 if (bp_ != scanpos) {
789 /* scan end of address */
790 for (ep_ = scanpos + 1;
791 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
793 prelast_dot = last_dot;
795 if (*(last_dot + 1) == '.') {
796 if (prelast_dot == NULL)
798 last_dot = prelast_dot;
803 /* TODO: really should terminate with an alnum? */
804 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
809 if (last_dot == NULL)
812 last_dot = prelast_dot;
813 if (last_dot == NULL || (scanpos + 1 >= last_dot))
817 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
818 if (*last_tld_char == '?')
821 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
828 if (!result) return FALSE;
830 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
831 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
834 /* see if this is <bracketed>; in this case we also scan for the informative part. */
835 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
838 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
839 #define IN_STACK() (ptr > closure_stack)
840 /* has underrun check */
841 #define POP_STACK() if(IN_STACK()) --ptr
842 /* has overrun check */
843 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
844 /* has underrun check */
845 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
849 /* scan for the informative part. */
850 for (bp_ -= 2; bp_ >= start; bp_--) {
851 /* if closure on the stack keep scanning */
852 if (PEEK_STACK() == *bp_) {
856 if (*bp_ == '\'' || *bp_ == '"') {
861 /* if nothing in the closure stack, do the special conditions
862 * the following if..else expression simply checks whether
863 * a token is acceptable. if not acceptable, the clause
864 * should terminate the loop with a 'break' */
867 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
868 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
869 /* hyphens are allowed, but only in
871 } else if (!ispunct(*bp_)) {
872 /* but anything not being a punctiation
875 break; /* anything else is rejected */
888 /* scan forward (should start with an alnum) */
889 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
899 #undef IS_ASCII_ALNUM
900 #undef IS_RFC822_CHAR
902 static gchar *make_email_string(const gchar *bp, const gchar *ep)
904 /* returns a mailto: URI; mailto: is also used to detect the
905 * uri type later on in the button_pressed signal handler */
909 tmp = g_strndup(bp, ep - bp);
910 result = g_strconcat("mailto:", tmp, NULL);
916 static gchar *make_http_string(const gchar *bp, const gchar *ep)
918 /* returns an http: URI; */
922 tmp = g_strndup(bp, ep - bp);
923 result = g_strconcat("http://", tmp, NULL);
929 #define ADD_TXT_POS(bp_, ep_, pti_) \
930 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
932 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
935 g_warning("alloc error scanning URIs\n"); \
936 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
942 /* textview_make_clickable_parts() - colorizes clickable parts */
943 static void textview_make_clickable_parts(TextView *textview,
945 const gchar *uri_tag,
946 const gchar *linebuf)
948 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
949 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
952 /* parse table - in order of priority */
954 const gchar *needle; /* token */
956 /* token search function */
957 gchar *(*search) (const gchar *haystack,
958 const gchar *needle);
959 /* part parsing function */
960 gboolean (*parse) (const gchar *start,
961 const gchar *scanpos,
964 /* part to URI function */
965 gchar *(*build_uri) (const gchar *bp,
969 static struct table parser[] = {
970 {"http://", strcasestr, get_uri_part, make_uri_string},
971 {"https://", strcasestr, get_uri_part, make_uri_string},
972 {"ftp://", strcasestr, get_uri_part, make_uri_string},
973 {"www.", strcasestr, get_uri_part, make_http_string},
974 {"mailto:", strcasestr, get_uri_part, make_uri_string},
975 {"@", strcasestr, get_email_part, make_email_string}
977 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
980 const gchar *walk, *bp, *ep;
983 const gchar *bp, *ep; /* text position */
984 gint pti; /* index in parse table */
985 struct txtpos *next; /* next */
986 } head = {NULL, NULL, 0, NULL}, *last = &head;
988 gtk_text_buffer_get_end_iter(buffer, &iter);
990 /* parse for clickable parts, and build a list of begin and end positions */
991 for (walk = linebuf, n = 0;;) {
992 gint last_index = PARSE_ELEMS;
993 gchar *scanpos = NULL;
995 /* FIXME: this looks phony. scanning for anything in the parse table */
996 for (n = 0; n < PARSE_ELEMS; n++) {
999 tmp = parser[n].search(walk, parser[n].needle);
1001 if (scanpos == NULL || tmp < scanpos) {
1009 /* check if URI can be parsed */
1010 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1011 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1012 ADD_TXT_POS(bp, ep, last_index);
1016 strlen(parser[last_index].needle);
1021 /* colorize this line */
1023 const gchar *normal_text = linebuf;
1026 for (last = head.next; last != NULL;
1027 normal_text = last->ep, last = last->next) {
1030 uri = g_new(RemoteURI, 1);
1031 if (last->bp - normal_text > 0)
1032 gtk_text_buffer_insert_with_tags_by_name
1035 last->bp - normal_text,
1037 uri->uri = parser[last->pti].build_uri(last->bp,
1039 uri->start = gtk_text_iter_get_offset(&iter);
1040 gtk_text_buffer_insert_with_tags_by_name
1042 last->bp, last->ep - last->bp,
1044 uri->end = gtk_text_iter_get_offset(&iter);
1045 textview->uri_list =
1046 g_slist_append(textview->uri_list, uri);
1050 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1054 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1062 static void textview_write_line(TextView *textview, const gchar *str,
1063 CodeConverter *conv)
1066 GtkTextBuffer *buffer;
1068 gchar buf[BUFFSIZE];
1070 gint quotelevel = -1;
1071 gchar quote_tag_str[10];
1073 text = GTK_TEXT_VIEW(textview->text);
1074 buffer = gtk_text_view_get_buffer(text);
1075 gtk_text_buffer_get_end_iter(buffer, &iter);
1078 strncpy2(buf, str, sizeof(buf));
1079 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1080 conv_localetodisp(buf, sizeof(buf), str);
1084 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1087 /* change color of quotation
1088 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1089 Up to 3 levels of quotations are detected, and each
1090 level is colored using a different color. */
1091 if (prefs_common.enable_color
1092 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1093 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1095 /* set up the correct foreground color */
1096 if (quotelevel > 2) {
1097 /* recycle colors */
1098 if (prefs_common.recycle_quote_colors)
1105 if (quotelevel == -1)
1108 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1109 "quote%d", quotelevel);
1110 fg_color = quote_tag_str;
1113 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1114 fg_color = "signature";
1115 textview->is_in_signature = TRUE;
1118 if (prefs_common.enable_color)
1119 textview_make_clickable_parts(textview, fg_color, "link", buf);
1121 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1124 void textview_write_link(TextView *textview, const gchar *str,
1125 const gchar *uri, CodeConverter *conv)
1127 GdkColor *link_color = NULL;
1129 GtkTextBuffer *buffer;
1131 gchar buf[BUFFSIZE];
1138 text = GTK_TEXT_VIEW(textview->text);
1139 buffer = gtk_text_view_get_buffer(text);
1140 gtk_text_buffer_get_end_iter(buffer, &iter);
1145 if (textview->text_is_mb)
1146 conv_localetodisp(buf, sizeof(buf), str);
1148 strncpy2(buf, str, sizeof(buf));
1149 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1150 conv_localetodisp(buf, sizeof(buf), str);
1151 else if (textview->text_is_mb)
1152 conv_unreadable_locale(buf);
1155 strncpy2(buf, str, sizeof(buf));
1156 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1157 conv_localetodisp(buf, sizeof(buf), str);
1162 gtk_text_buffer_get_end_iter(buffer, &iter);
1164 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1165 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1167 if (prefs_common.enable_color) {
1168 link_color = &uri_color;
1170 r_uri = g_new(RemoteURI, 1);
1171 r_uri->uri = g_strdup(uri);
1172 r_uri->start = gtk_text_iter_get_offset(&iter);
1173 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1175 r_uri->end = gtk_text_iter_get_offset(&iter);
1176 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1179 void textview_clear(TextView *textview)
1181 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1182 GtkTextBuffer *buffer;
1184 buffer = gtk_text_view_get_buffer(text);
1185 gtk_text_buffer_set_text(buffer, "\0", -1);
1187 TEXTVIEW_STATUSBAR_POP(textview);
1188 textview_uri_list_remove_all(textview->uri_list);
1189 textview->uri_list = NULL;
1191 textview->body_pos = 0;
1194 void textview_destroy(TextView *textview)
1196 textview_uri_list_remove_all(textview->uri_list);
1197 textview->uri_list = NULL;
1202 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1204 textview->show_all_headers = all_headers;
1207 void textview_set_font(TextView *textview, const gchar *codeset)
1209 if (prefs_common.textfont) {
1210 PangoFontDescription *font_desc = NULL;
1212 if (prefs_common.textfont)
1213 font_desc = pango_font_description_from_string
1214 (prefs_common.textfont);
1216 gtk_widget_modify_font(textview->text, font_desc);
1217 pango_font_description_free(font_desc);
1220 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1221 prefs_common.line_space / 2);
1222 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1223 prefs_common.line_space / 2);
1224 if (prefs_common.head_space) {
1225 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1227 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1231 void textview_set_text(TextView *textview, const gchar *text)
1234 GtkTextBuffer *buffer;
1236 g_return_if_fail(textview != NULL);
1237 g_return_if_fail(text != NULL);
1239 textview_clear(textview);
1241 view = GTK_TEXT_VIEW(textview->text);
1242 buffer = gtk_text_view_get_buffer(view);
1243 gtk_text_buffer_set_text(buffer, text, strlen(text));
1259 H_ORGANIZATION = 11,
1262 void textview_set_position(TextView *textview, gint pos)
1264 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1265 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1268 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1269 gtk_text_buffer_place_cursor(buffer, &iter);
1270 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1273 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1275 gchar buf[BUFFSIZE];
1276 GPtrArray *headers, *sorted_headers;
1277 GSList *disphdr_list;
1281 g_return_val_if_fail(fp != NULL, NULL);
1283 if (textview->show_all_headers)
1284 return procheader_get_header_array_asis(fp);
1286 if (!prefs_common.display_header) {
1287 while (fgets(buf, sizeof(buf), fp) != NULL)
1288 if (buf[0] == '\r' || buf[0] == '\n') break;
1292 headers = procheader_get_header_array_asis(fp);
1294 sorted_headers = g_ptr_array_new();
1296 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1297 disphdr_list = disphdr_list->next) {
1298 DisplayHeaderProp *dp =
1299 (DisplayHeaderProp *)disphdr_list->data;
1301 for (i = 0; i < headers->len; i++) {
1302 header = g_ptr_array_index(headers, i);
1304 if (procheader_headername_equal(header->name,
1307 procheader_header_free(header);
1309 g_ptr_array_add(sorted_headers, header);
1311 g_ptr_array_remove_index(headers, i);
1317 if (prefs_common.show_other_header) {
1318 for (i = 0; i < headers->len; i++) {
1319 header = g_ptr_array_index(headers, i);
1320 g_ptr_array_add(sorted_headers, header);
1322 g_ptr_array_free(headers, TRUE);
1324 procheader_header_array_destroy(headers);
1327 return sorted_headers;
1330 static void textview_show_header(TextView *textview, GPtrArray *headers)
1332 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1333 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1338 g_return_if_fail(headers != NULL);
1340 for (i = 0; i < headers->len; i++) {
1341 header = g_ptr_array_index(headers, i);
1342 g_return_if_fail(header->name != NULL);
1344 gtk_text_buffer_get_end_iter (buffer, &iter);
1345 gtk_text_buffer_insert_with_tags_by_name
1346 (buffer, &iter, header->name, -1,
1347 "header_title", "header", NULL);
1348 if (header->name[strlen(header->name) - 1] != ' ')
1349 gtk_text_buffer_insert_with_tags_by_name
1350 (buffer, &iter, " ", 1,
1351 "header_title", "header", NULL);
1353 if (procheader_headername_equal(header->name, "Subject") ||
1354 procheader_headername_equal(header->name, "From") ||
1355 procheader_headername_equal(header->name, "To") ||
1356 procheader_headername_equal(header->name, "Cc"))
1357 unfold_line(header->body);
1361 if (textview->text_is_mb == TRUE)
1362 conv_unreadable_locale(header->body);
1365 if (prefs_common.enable_color &&
1366 (procheader_headername_equal(header->name, "X-Mailer") ||
1367 procheader_headername_equal(header->name,
1369 strstr(header->body, "Sylpheed") != NULL) {
1370 gtk_text_buffer_get_end_iter (buffer, &iter);
1371 gtk_text_buffer_insert_with_tags_by_name
1372 (buffer, &iter, header->body, -1,
1373 "header", "emphasis", NULL);
1374 } else if (prefs_common.enable_color) {
1375 textview_make_clickable_parts(textview, "header", "link",
1378 textview_make_clickable_parts(textview, "header", NULL,
1381 gtk_text_buffer_get_end_iter (buffer, &iter);
1382 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1387 gboolean textview_search_string(TextView *textview, const gchar *str,
1390 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1391 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1393 GtkTextIter iter, start, end, real_end, *pos;
1394 gboolean found = FALSE;
1395 gint insert_offset, selbound_offset;
1397 /* reset selection */
1398 mark = gtk_text_buffer_get_mark(buffer, "insert");
1399 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1400 insert_offset = gtk_text_iter_get_offset(&start);
1401 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1402 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1403 selbound_offset = gtk_text_iter_get_offset(&end);
1405 pos = insert_offset > selbound_offset ? &start : &end;
1406 gtk_text_buffer_place_cursor(buffer, pos);
1409 mark = gtk_text_buffer_get_insert(buffer);
1410 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1412 found = gtk_text_iter_forward_search(&iter, str,
1413 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1414 &start, &end, NULL);
1418 gtk_text_buffer_get_end_iter(buffer, &real_end);
1419 text = strdup(gtk_text_buffer_get_text(buffer, &iter,
1422 while (!found && i++ < strlen(text) - 1) {
1423 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1426 i += gtk_text_iter_get_offset(&end);
1429 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1430 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1438 gtk_text_buffer_place_cursor(buffer, &start);
1439 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1441 mark = gtk_text_buffer_get_mark(buffer, "insert");
1442 gtk_text_view_scroll_mark_onscreen(text, mark);
1448 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1451 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1452 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1454 GtkTextIter iter, start, real_start, end, *pos;
1455 gboolean found = FALSE;
1456 gint insert_offset, selbound_offset;
1458 /* reset selection */
1459 mark = gtk_text_buffer_get_mark(buffer, "insert");
1460 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1461 insert_offset = gtk_text_iter_get_offset(&start);
1462 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1463 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1464 selbound_offset = gtk_text_iter_get_offset(&end);
1466 pos = insert_offset < selbound_offset ? &start : &end;
1467 gtk_text_buffer_place_cursor(buffer, pos);
1470 mark = gtk_text_buffer_get_insert(buffer);
1471 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1473 found = gtk_text_iter_backward_search(&iter, str,
1474 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1475 &start, &end, NULL);
1479 if (gtk_text_iter_get_offset(&iter) == 0)
1480 gtk_text_buffer_get_end_iter(buffer, &iter);
1482 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1483 gtk_text_buffer_get_start_iter(buffer, &real_start);
1485 text = strdup(gtk_text_buffer_get_text(buffer, &real_start,
1488 while (!found && i-- > 0) {
1489 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1493 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1494 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1502 gtk_text_buffer_place_cursor(buffer, &end);
1503 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1505 mark = gtk_text_buffer_get_mark(buffer, "insert");
1506 gtk_text_view_scroll_mark_onscreen(text, mark);
1512 void textview_scroll_one_line(TextView *textview, gboolean up)
1514 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1515 GtkAdjustment *vadj = text->vadjustment;
1518 if (prefs_common.enable_smooth_scroll) {
1519 textview_smooth_scroll_one_line(textview, up);
1524 upper = vadj->upper - vadj->page_size;
1525 if (vadj->value < upper) {
1527 vadj->step_increment * 4;
1529 MIN(vadj->value, upper);
1530 g_signal_emit_by_name(G_OBJECT(vadj),
1531 "value_changed", 0);
1534 if (vadj->value > 0.0) {
1536 vadj->step_increment * 4;
1538 MAX(vadj->value, 0.0);
1539 g_signal_emit_by_name(G_OBJECT(vadj),
1540 "value_changed", 0);
1545 gboolean textview_scroll_page(TextView *textview, gboolean up)
1547 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1548 GtkAdjustment *vadj = text->vadjustment;
1552 if (prefs_common.enable_smooth_scroll)
1553 return textview_smooth_scroll_page(textview, up);
1555 if (prefs_common.scroll_halfpage)
1556 page_incr = vadj->page_increment / 2;
1558 page_incr = vadj->page_increment;
1561 upper = vadj->upper - vadj->page_size;
1562 if (vadj->value < upper) {
1563 vadj->value += page_incr;
1564 vadj->value = MIN(vadj->value, upper);
1565 g_signal_emit_by_name(G_OBJECT(vadj),
1566 "value_changed", 0);
1570 if (vadj->value > 0.0) {
1571 vadj->value -= page_incr;
1572 vadj->value = MAX(vadj->value, 0.0);
1573 g_signal_emit_by_name(G_OBJECT(vadj),
1574 "value_changed", 0);
1582 static void textview_smooth_scroll_do(TextView *textview,
1583 gfloat old_value, gfloat last_value,
1586 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1587 GtkAdjustment *vadj = text->vadjustment;
1592 if (old_value < last_value) {
1593 change_value = last_value - old_value;
1596 change_value = old_value - last_value;
1600 for (i = step; i <= change_value; i += step) {
1601 vadj->value = old_value + (up ? -i : i);
1602 g_signal_emit_by_name(G_OBJECT(vadj),
1603 "value_changed", 0);
1606 vadj->value = last_value;
1607 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1610 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1612 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1613 GtkAdjustment *vadj = text->vadjustment;
1619 upper = vadj->upper - vadj->page_size;
1620 if (vadj->value < upper) {
1621 old_value = vadj->value;
1622 last_value = vadj->value +
1623 vadj->step_increment * 4;
1624 last_value = MIN(last_value, upper);
1626 textview_smooth_scroll_do(textview, old_value,
1628 prefs_common.scroll_step);
1631 if (vadj->value > 0.0) {
1632 old_value = vadj->value;
1633 last_value = vadj->value -
1634 vadj->step_increment * 4;
1635 last_value = MAX(last_value, 0.0);
1637 textview_smooth_scroll_do(textview, old_value,
1639 prefs_common.scroll_step);
1644 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1646 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1647 GtkAdjustment *vadj = text->vadjustment;
1653 if (prefs_common.scroll_halfpage)
1654 page_incr = vadj->page_increment / 2;
1656 page_incr = vadj->page_increment;
1659 upper = vadj->upper - vadj->page_size;
1660 if (vadj->value < upper) {
1661 old_value = vadj->value;
1662 last_value = vadj->value + page_incr;
1663 last_value = MIN(last_value, upper);
1665 textview_smooth_scroll_do(textview, old_value,
1667 prefs_common.scroll_step);
1671 if (vadj->value > 0.0) {
1672 old_value = vadj->value;
1673 last_value = vadj->value - page_incr;
1674 last_value = MAX(last_value, 0.0);
1676 textview_smooth_scroll_do(textview, old_value,
1678 prefs_common.scroll_step);
1686 #define KEY_PRESS_EVENT_STOP() \
1687 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1690 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1693 SummaryView *summaryview = NULL;
1694 MessageView *messageview = textview->messageview;
1696 if (!event) return FALSE;
1697 if (messageview->mainwin)
1698 summaryview = messageview->mainwin->summaryview;
1700 switch (event->keyval) {
1715 summary_pass_key_press_event(summaryview, event);
1717 textview_scroll_page(textview, FALSE);
1720 textview_scroll_page(textview, TRUE);
1723 textview_scroll_one_line(textview,
1724 (event->state & GDK_MOD1_MASK) != 0);
1728 summary_pass_key_press_event(summaryview, event);
1733 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1734 KEY_PRESS_EVENT_STOP();
1735 mimeview_pass_key_press_event(messageview->mimeview,
1739 /* possible fall through */
1742 event->window != messageview->mainwin->window->window) {
1743 GdkEventKey tmpev = *event;
1745 tmpev.window = messageview->mainwin->window->window;
1746 KEY_PRESS_EVENT_STOP();
1747 gtk_widget_event(messageview->mainwin->window,
1748 (GdkEvent *)&tmpev);
1756 static gint show_url_timeout_cb(gpointer data)
1758 TextView *textview = (TextView *)data;
1760 TEXTVIEW_STATUSBAR_POP(textview);
1761 textview->show_url_timeout_tag = 0;
1766 *\brief Check to see if a web URL has been disguised as a different
1767 * URL (possible with HTML email).
1769 *\param uri The uri to check
1771 *\param textview The TextView the URL is contained in
1773 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1774 * it anyway, otherwise FALSE
1776 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1779 gboolean retval = TRUE;
1781 if (g_strncasecmp(uri->uri, "http:", 5) &&
1782 g_strncasecmp(uri->uri, "https:", 6) &&
1783 g_strncasecmp(uri->uri, "www.", 4))
1786 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1789 if (clicked_str == NULL)
1792 if (strcmp(clicked_str, uri->uri) &&
1793 (!g_strncasecmp(clicked_str, "http:", 5) ||
1794 !g_strncasecmp(clicked_str, "https:", 6) ||
1795 !g_strncasecmp(clicked_str, "www.", 4))) {
1799 /* allow uri->uri == http://somewhere.com
1800 and clicked_str == somewhere.com */
1801 str = g_strconcat("http://", clicked_str, NULL);
1803 if (!g_strcasecmp(str, uri->uri))
1808 if (retval == FALSE) {
1812 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1813 "the apparent URL (%s). \n"
1815 uri->uri, clicked_str);
1816 resp = alertpanel_with_type(_("Warning"),
1820 NULL, NULL, ALERT_WARNING);
1822 if (resp == G_ALERTDEFAULT)
1825 g_free(clicked_str);
1829 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1830 GdkEvent *event, GtkTextIter *iter,
1833 GtkTextIter start_iter, end_iter;
1834 gint start_pos, end_pos;
1835 GdkEventButton *bevent;
1838 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
1839 && event->type != GDK_MOTION_NOTIFY)
1842 bevent = (GdkEventButton *) event;
1844 /* get start and end positions */
1846 if(!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) {
1847 debug_print("Can't find start.");
1850 start_pos = gtk_text_iter_get_offset(&start_iter);
1853 if(!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) {
1854 debug_print("Can't find end");
1857 end_pos = gtk_text_iter_get_offset(&end_iter);
1859 /* search current uri */
1860 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1861 RemoteURI *uri = (RemoteURI *)cur->data;
1863 if (start_pos != uri->start || end_pos != uri->end)
1866 trimmed_uri = trim_string(uri->uri, 60);
1867 /* hover or single click: display url in statusbar */
1869 if (event->type == GDK_MOTION_NOTIFY
1870 || (event->type == GDK_BUTTON_PRESS && bevent->button == 1)) {
1871 if (textview->messageview->mainwin) {
1872 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1873 textview->show_url_timeout_tag = gtk_timeout_add
1874 (4000, show_url_timeout_cb, textview);
1878 /* doubleclick: open compose / add address / browser */
1879 if ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) ||
1880 bevent->button == 2 || bevent->button == 3) {
1881 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1882 if (bevent->button == 3) {
1883 gchar *fromname, *fromaddress;
1886 fromaddress = g_strdup(uri->uri + 7);
1887 /* Hiroyuki: please put this function in utils.c! */
1888 fromname = procheader_get_fromname(fromaddress);
1889 extract_address(fromaddress);
1890 g_message("adding from textview %s <%s>", fromname, fromaddress);
1891 /* Add to address book - Match */
1892 addressbook_add_contact( fromname, fromaddress, NULL );
1894 g_free(fromaddress);
1897 PrefsAccount *account = NULL;
1899 if (textview->messageview && textview->messageview->msginfo &&
1900 textview->messageview->msginfo->folder) {
1901 FolderItem *folder_item;
1903 folder_item = textview->messageview->msginfo->folder;
1904 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1905 account = account_find_from_id(folder_item->prefs->default_account);
1907 compose_new(account, uri->uri + 7, NULL);
1911 if (textview_uri_security_check(textview, uri) == TRUE)
1913 prefs_common.uri_cmd);
1917 g_free(trimmed_uri);
1924 *\brief Check to see if a web URL has been disguised as a different
1925 * URL (possible with HTML email).
1927 *\param uri The uri to check
1929 *\param textview The TextView the URL is contained in
1931 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1932 * it anyway, otherwise FALSE
1934 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
1937 gboolean retval = TRUE;
1939 if (is_uri_string(uri->uri) == FALSE)
1942 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1943 uri->start, uri->end);
1944 if (visible_str == NULL)
1947 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
1949 gchar *visible_uri_path;
1951 uri_path = get_uri_path(uri->uri);
1952 visible_uri_path = get_uri_path(visible_str);
1953 if (strcmp(uri_path, visible_uri_path) != 0)
1957 if (retval == FALSE) {
1961 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1962 "the apparent URL (%s).\n"
1964 uri->uri, visible_str);
1965 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"), _("No"), NULL,
1966 NULL, ALERT_WARNING);
1968 if (aval == G_ALERTDEFAULT)
1972 g_free(visible_str);
1977 static void textview_uri_list_remove_all(GSList *uri_list)
1981 for (cur = uri_list; cur != NULL; cur = cur->next) {
1983 g_free(((RemoteURI *)cur->data)->uri);
1988 g_slist_free(uri_list);