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->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 (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 (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->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 = 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 !isascii(*(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) (isascii(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 strncpy2(buf, str, sizeof(buf));
1083 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1086 /* change color of quotation
1087 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1088 Up to 3 levels of quotations are detected, and each
1089 level is colored using a different color. */
1090 if (prefs_common.enable_color
1091 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1092 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1094 /* set up the correct foreground color */
1095 if (quotelevel > 2) {
1096 /* recycle colors */
1097 if (prefs_common.recycle_quote_colors)
1104 if (quotelevel == -1)
1107 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1108 "quote%d", quotelevel);
1109 fg_color = quote_tag_str;
1112 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1113 fg_color = "signature";
1114 textview->is_in_signature = TRUE;
1117 if (prefs_common.enable_color)
1118 textview_make_clickable_parts(textview, fg_color, "link", buf);
1120 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1123 void textview_write_link(TextView *textview, const gchar *str,
1124 const gchar *uri, CodeConverter *conv)
1126 GdkColor *link_color = NULL;
1128 GtkTextBuffer *buffer;
1130 gchar buf[BUFFSIZE];
1137 text = GTK_TEXT_VIEW(textview->text);
1138 buffer = gtk_text_view_get_buffer(text);
1139 gtk_text_buffer_get_end_iter(buffer, &iter);
1144 if (textview->text_is_mb)
1145 conv_localetodisp(buf, sizeof(buf), str);
1147 strncpy2(buf, str, sizeof(buf));
1148 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1149 conv_localetodisp(buf, sizeof(buf), str);
1150 else if (textview->text_is_mb)
1151 conv_unreadable_locale(buf);
1154 strncpy2(buf, str, sizeof(buf));
1155 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1156 conv_localetodisp(buf, sizeof(buf), str);
1161 gtk_text_buffer_get_end_iter(buffer, &iter);
1163 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1164 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1166 if (prefs_common.enable_color) {
1167 link_color = &uri_color;
1169 r_uri = g_new(RemoteURI, 1);
1170 r_uri->uri = g_strdup(uri);
1171 r_uri->start = gtk_text_iter_get_offset(&iter);
1172 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1174 r_uri->end = gtk_text_iter_get_offset(&iter);
1175 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1178 void textview_clear(TextView *textview)
1180 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1181 GtkTextBuffer *buffer;
1183 buffer = gtk_text_view_get_buffer(text);
1184 gtk_text_buffer_set_text(buffer, "\0", -1);
1186 TEXTVIEW_STATUSBAR_POP(textview);
1187 textview_uri_list_remove_all(textview->uri_list);
1188 textview->uri_list = NULL;
1190 textview->body_pos = 0;
1193 void textview_destroy(TextView *textview)
1195 textview_uri_list_remove_all(textview->uri_list);
1196 textview->uri_list = NULL;
1201 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1203 textview->show_all_headers = all_headers;
1206 void textview_set_font(TextView *textview, const gchar *codeset)
1208 if (prefs_common.textfont) {
1209 PangoFontDescription *font_desc = NULL;
1211 if (prefs_common.textfont)
1212 font_desc = pango_font_description_from_string
1213 (prefs_common.textfont);
1215 gtk_widget_modify_font(textview->text, font_desc);
1216 pango_font_description_free(font_desc);
1219 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1220 prefs_common.line_space / 2);
1221 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1222 prefs_common.line_space / 2);
1223 if (prefs_common.head_space) {
1224 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1226 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1230 void textview_set_text(TextView *textview, const gchar *text)
1233 GtkTextBuffer *buffer;
1235 g_return_if_fail(textview != NULL);
1236 g_return_if_fail(text != NULL);
1238 textview_clear(textview);
1240 view = GTK_TEXT_VIEW(textview->text);
1241 buffer = gtk_text_view_get_buffer(view);
1242 gtk_text_buffer_set_text(buffer, text, strlen(text));
1258 H_ORGANIZATION = 11,
1261 void textview_set_position(TextView *textview, gint pos)
1263 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1264 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1267 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1268 gtk_text_buffer_place_cursor(buffer, &iter);
1269 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1272 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1274 gchar buf[BUFFSIZE];
1275 GPtrArray *headers, *sorted_headers;
1276 GSList *disphdr_list;
1280 g_return_val_if_fail(fp != NULL, NULL);
1282 if (textview->show_all_headers)
1283 return procheader_get_header_array_asis(fp);
1285 if (!prefs_common.display_header) {
1286 while (fgets(buf, sizeof(buf), fp) != NULL)
1287 if (buf[0] == '\r' || buf[0] == '\n') break;
1291 headers = procheader_get_header_array_asis(fp);
1293 sorted_headers = g_ptr_array_new();
1295 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1296 disphdr_list = disphdr_list->next) {
1297 DisplayHeaderProp *dp =
1298 (DisplayHeaderProp *)disphdr_list->data;
1300 for (i = 0; i < headers->len; i++) {
1301 header = g_ptr_array_index(headers, i);
1303 if (procheader_headername_equal(header->name,
1306 procheader_header_free(header);
1308 g_ptr_array_add(sorted_headers, header);
1310 g_ptr_array_remove_index(headers, i);
1316 if (prefs_common.show_other_header) {
1317 for (i = 0; i < headers->len; i++) {
1318 header = g_ptr_array_index(headers, i);
1319 g_ptr_array_add(sorted_headers, header);
1321 g_ptr_array_free(headers, TRUE);
1323 procheader_header_array_destroy(headers);
1326 return sorted_headers;
1329 static void textview_show_header(TextView *textview, GPtrArray *headers)
1331 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1332 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1337 g_return_if_fail(headers != NULL);
1339 for (i = 0; i < headers->len; i++) {
1340 header = g_ptr_array_index(headers, i);
1341 g_return_if_fail(header->name != NULL);
1343 gtk_text_buffer_get_end_iter (buffer, &iter);
1344 gtk_text_buffer_insert_with_tags_by_name
1345 (buffer, &iter, header->name, -1,
1346 "header_title", "header", NULL);
1347 if (header->name[strlen(header->name) - 1] != ' ')
1348 gtk_text_buffer_insert_with_tags_by_name
1349 (buffer, &iter, " ", 1,
1350 "header_title", "header", NULL);
1352 if (procheader_headername_equal(header->name, "Subject") ||
1353 procheader_headername_equal(header->name, "From") ||
1354 procheader_headername_equal(header->name, "To") ||
1355 procheader_headername_equal(header->name, "Cc"))
1356 unfold_line(header->body);
1360 if (textview->text_is_mb == TRUE)
1361 conv_unreadable_locale(header->body);
1364 if (prefs_common.enable_color &&
1365 (procheader_headername_equal(header->name, "X-Mailer") ||
1366 procheader_headername_equal(header->name,
1368 strstr(header->body, "Sylpheed") != NULL) {
1369 gtk_text_buffer_get_end_iter (buffer, &iter);
1370 gtk_text_buffer_insert_with_tags_by_name
1371 (buffer, &iter, header->body, -1,
1372 "header", "emphasis", NULL);
1373 } else if (prefs_common.enable_color) {
1374 textview_make_clickable_parts(textview, "header", "link",
1377 textview_make_clickable_parts(textview, "header", NULL,
1380 gtk_text_buffer_get_end_iter (buffer, &iter);
1381 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1386 gboolean textview_search_string(TextView *textview, const gchar *str,
1389 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1390 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1392 GtkTextIter iter, start, end, real_end, *pos;
1393 gboolean found = FALSE;
1394 gint insert_offset, selbound_offset;
1396 /* reset selection */
1397 mark = gtk_text_buffer_get_mark(buffer, "insert");
1398 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1399 insert_offset = gtk_text_iter_get_offset(&start);
1400 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1401 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1402 selbound_offset = gtk_text_iter_get_offset(&end);
1404 pos = insert_offset > selbound_offset ? &start : &end;
1405 gtk_text_buffer_place_cursor(buffer, pos);
1408 mark = gtk_text_buffer_get_insert(buffer);
1409 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1411 found = gtk_text_iter_forward_search(&iter, str,
1412 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1413 &start, &end, NULL);
1417 gtk_text_buffer_get_end_iter(buffer, &real_end);
1418 text = strdup(gtk_text_buffer_get_text(buffer, &iter,
1421 while (!found && i++ < strlen(text) - 1) {
1422 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1425 i += gtk_text_iter_get_offset(&end);
1428 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1429 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1437 gtk_text_buffer_place_cursor(buffer, &start);
1438 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1440 mark = gtk_text_buffer_get_mark(buffer, "insert");
1441 gtk_text_view_scroll_mark_onscreen(text, mark);
1447 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1450 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1451 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1453 GtkTextIter iter, start, real_start, end, *pos;
1454 gboolean found = FALSE;
1455 gint insert_offset, selbound_offset;
1457 /* reset selection */
1458 mark = gtk_text_buffer_get_mark(buffer, "insert");
1459 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1460 insert_offset = gtk_text_iter_get_offset(&start);
1461 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1462 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1463 selbound_offset = gtk_text_iter_get_offset(&end);
1465 pos = insert_offset < selbound_offset ? &start : &end;
1466 gtk_text_buffer_place_cursor(buffer, pos);
1469 mark = gtk_text_buffer_get_insert(buffer);
1470 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1472 found = gtk_text_iter_backward_search(&iter, str,
1473 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1474 &start, &end, NULL);
1478 if (gtk_text_iter_get_offset(&iter) == 0)
1479 gtk_text_buffer_get_end_iter(buffer, &iter);
1481 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1482 gtk_text_buffer_get_start_iter(buffer, &real_start);
1484 text = strdup(gtk_text_buffer_get_text(buffer, &real_start,
1487 while (!found && i-- > 0) {
1488 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1492 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1493 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1501 gtk_text_buffer_place_cursor(buffer, &end);
1502 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1504 mark = gtk_text_buffer_get_mark(buffer, "insert");
1505 gtk_text_view_scroll_mark_onscreen(text, mark);
1511 void textview_scroll_one_line(TextView *textview, gboolean up)
1513 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1514 GtkAdjustment *vadj = text->vadjustment;
1517 if (prefs_common.enable_smooth_scroll) {
1518 textview_smooth_scroll_one_line(textview, up);
1523 upper = vadj->upper - vadj->page_size;
1524 if (vadj->value < upper) {
1526 vadj->step_increment * 4;
1528 MIN(vadj->value, upper);
1529 g_signal_emit_by_name(G_OBJECT(vadj),
1530 "value_changed", 0);
1533 if (vadj->value > 0.0) {
1535 vadj->step_increment * 4;
1537 MAX(vadj->value, 0.0);
1538 g_signal_emit_by_name(G_OBJECT(vadj),
1539 "value_changed", 0);
1544 gboolean textview_scroll_page(TextView *textview, gboolean up)
1546 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1547 GtkAdjustment *vadj = text->vadjustment;
1551 if (prefs_common.enable_smooth_scroll)
1552 return textview_smooth_scroll_page(textview, up);
1554 if (prefs_common.scroll_halfpage)
1555 page_incr = vadj->page_increment / 2;
1557 page_incr = vadj->page_increment;
1560 upper = vadj->upper - vadj->page_size;
1561 if (vadj->value < upper) {
1562 vadj->value += page_incr;
1563 vadj->value = MIN(vadj->value, upper);
1564 g_signal_emit_by_name(G_OBJECT(vadj),
1565 "value_changed", 0);
1569 if (vadj->value > 0.0) {
1570 vadj->value -= page_incr;
1571 vadj->value = MAX(vadj->value, 0.0);
1572 g_signal_emit_by_name(G_OBJECT(vadj),
1573 "value_changed", 0);
1581 static void textview_smooth_scroll_do(TextView *textview,
1582 gfloat old_value, gfloat last_value,
1585 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1586 GtkAdjustment *vadj = text->vadjustment;
1591 if (old_value < last_value) {
1592 change_value = last_value - old_value;
1595 change_value = old_value - last_value;
1599 for (i = step; i <= change_value; i += step) {
1600 vadj->value = old_value + (up ? -i : i);
1601 g_signal_emit_by_name(G_OBJECT(vadj),
1602 "value_changed", 0);
1605 vadj->value = last_value;
1606 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1609 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1611 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1612 GtkAdjustment *vadj = text->vadjustment;
1618 upper = vadj->upper - vadj->page_size;
1619 if (vadj->value < upper) {
1620 old_value = vadj->value;
1621 last_value = vadj->value +
1622 vadj->step_increment * 4;
1623 last_value = MIN(last_value, upper);
1625 textview_smooth_scroll_do(textview, old_value,
1627 prefs_common.scroll_step);
1630 if (vadj->value > 0.0) {
1631 old_value = vadj->value;
1632 last_value = vadj->value -
1633 vadj->step_increment * 4;
1634 last_value = MAX(last_value, 0.0);
1636 textview_smooth_scroll_do(textview, old_value,
1638 prefs_common.scroll_step);
1643 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1645 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1646 GtkAdjustment *vadj = text->vadjustment;
1652 if (prefs_common.scroll_halfpage)
1653 page_incr = vadj->page_increment / 2;
1655 page_incr = vadj->page_increment;
1658 upper = vadj->upper - vadj->page_size;
1659 if (vadj->value < upper) {
1660 old_value = vadj->value;
1661 last_value = vadj->value + page_incr;
1662 last_value = MIN(last_value, upper);
1664 textview_smooth_scroll_do(textview, old_value,
1666 prefs_common.scroll_step);
1670 if (vadj->value > 0.0) {
1671 old_value = vadj->value;
1672 last_value = vadj->value - page_incr;
1673 last_value = MAX(last_value, 0.0);
1675 textview_smooth_scroll_do(textview, old_value,
1677 prefs_common.scroll_step);
1685 #define KEY_PRESS_EVENT_STOP() \
1686 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1689 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1692 SummaryView *summaryview = NULL;
1693 MessageView *messageview = textview->messageview;
1695 if (!event) return FALSE;
1696 if (messageview->mainwin)
1697 summaryview = messageview->mainwin->summaryview;
1699 switch (event->keyval) {
1714 summary_pass_key_press_event(summaryview, event);
1716 textview_scroll_page(textview, FALSE);
1719 textview_scroll_page(textview, TRUE);
1722 textview_scroll_one_line(textview,
1723 (event->state & GDK_MOD1_MASK) != 0);
1727 summary_pass_key_press_event(summaryview, event);
1732 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1733 KEY_PRESS_EVENT_STOP();
1734 mimeview_pass_key_press_event(messageview->mimeview,
1738 /* possible fall through */
1741 event->window != messageview->mainwin->window->window) {
1742 GdkEventKey tmpev = *event;
1744 tmpev.window = messageview->mainwin->window->window;
1745 KEY_PRESS_EVENT_STOP();
1746 gtk_widget_event(messageview->mainwin->window,
1747 (GdkEvent *)&tmpev);
1755 static gint show_url_timeout_cb(gpointer data)
1757 TextView *textview = (TextView *)data;
1759 TEXTVIEW_STATUSBAR_POP(textview);
1760 textview->show_url_timeout_tag = 0;
1765 *\brief Check to see if a web URL has been disguised as a different
1766 * URL (possible with HTML email).
1768 *\param uri The uri to check
1770 *\param textview The TextView the URL is contained in
1772 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1773 * it anyway, otherwise FALSE
1775 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1778 gboolean retval = TRUE;
1780 if (g_strncasecmp(uri->uri, "http:", 5) &&
1781 g_strncasecmp(uri->uri, "https:", 6) &&
1782 g_strncasecmp(uri->uri, "www.", 4))
1785 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1788 if (clicked_str == NULL)
1791 if (strcmp(clicked_str, uri->uri) &&
1792 (!g_strncasecmp(clicked_str, "http:", 5) ||
1793 !g_strncasecmp(clicked_str, "https:", 6) ||
1794 !g_strncasecmp(clicked_str, "www.", 4))) {
1798 /* allow uri->uri == http://somewhere.com
1799 and clicked_str == somewhere.com */
1800 str = g_strconcat("http://", clicked_str, NULL);
1802 if (!g_strcasecmp(str, uri->uri))
1807 if (retval == FALSE) {
1811 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1812 "the apparent URL (%s). \n"
1814 uri->uri, clicked_str);
1815 resp = alertpanel_with_type(_("Warning"),
1819 NULL, NULL, ALERT_WARNING);
1821 if (resp == G_ALERTDEFAULT)
1824 g_free(clicked_str);
1828 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1829 GdkEvent *event, GtkTextIter *iter,
1832 GtkTextIter start_iter, end_iter;
1833 gint start_pos, end_pos;
1834 GdkEventButton *bevent;
1837 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
1838 && event->type != GDK_MOTION_NOTIFY)
1841 bevent = (GdkEventButton *) event;
1843 /* get start and end positions */
1845 if(!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) {
1846 debug_print("Can't find start.");
1849 start_pos = gtk_text_iter_get_offset(&start_iter);
1852 if(!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) {
1853 debug_print("Can't find end");
1856 end_pos = gtk_text_iter_get_offset(&end_iter);
1858 /* search current uri */
1859 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1860 RemoteURI *uri = (RemoteURI *)cur->data;
1862 if (start_pos != uri->start || end_pos != uri->end)
1865 trimmed_uri = trim_string(uri->uri, 60);
1866 /* hover or single click: display url in statusbar */
1868 if (event->type == GDK_MOTION_NOTIFY
1869 || (event->type == GDK_BUTTON_PRESS && bevent->button == 1)) {
1870 if (textview->messageview->mainwin) {
1871 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1872 textview->show_url_timeout_tag = gtk_timeout_add
1873 (4000, show_url_timeout_cb, textview);
1877 /* doubleclick: open compose / add address / browser */
1878 if ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) ||
1879 bevent->button == 2 || bevent->button == 3) {
1880 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1881 if (bevent->button == 3) {
1882 gchar *fromname, *fromaddress;
1885 fromaddress = g_strdup(uri->uri + 7);
1886 /* Hiroyuki: please put this function in utils.c! */
1887 fromname = procheader_get_fromname(fromaddress);
1888 extract_address(fromaddress);
1889 g_message("adding from textview %s <%s>", fromname, fromaddress);
1890 /* Add to address book - Match */
1891 addressbook_add_contact( fromname, fromaddress, NULL );
1893 g_free(fromaddress);
1896 PrefsAccount *account = NULL;
1898 if (textview->messageview && textview->messageview->msginfo &&
1899 textview->messageview->msginfo->folder) {
1900 FolderItem *folder_item;
1902 folder_item = textview->messageview->msginfo->folder;
1903 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1904 account = account_find_from_id(folder_item->prefs->default_account);
1906 compose_new(account, uri->uri + 7, NULL);
1910 if (textview_uri_security_check(textview, uri) == TRUE)
1912 prefs_common.uri_cmd);
1916 g_free(trimmed_uri);
1923 *\brief Check to see if a web URL has been disguised as a different
1924 * URL (possible with HTML email).
1926 *\param uri The uri to check
1928 *\param textview The TextView the URL is contained in
1930 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1931 * it anyway, otherwise FALSE
1933 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
1936 gboolean retval = TRUE;
1938 if (is_uri_string(uri->uri) == FALSE)
1941 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1942 uri->start, uri->end);
1943 if (visible_str == NULL)
1946 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
1948 gchar *visible_uri_path;
1950 uri_path = get_uri_path(uri->uri);
1951 visible_uri_path = get_uri_path(visible_str);
1952 if (strcmp(uri_path, visible_uri_path) != 0)
1956 if (retval == FALSE) {
1960 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1961 "the apparent URL (%s).\n"
1963 uri->uri, visible_str);
1964 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"), _("No"), NULL,
1965 NULL, ALERT_WARNING);
1967 if (aval == G_ALERTDEFAULT)
1971 g_free(visible_str);
1976 static void textview_uri_list_remove_all(GSList *uri_list)
1980 for (cur = uri_list; cur != NULL; cur = cur->next) {
1982 g_free(((RemoteURI *)cur->data)->uri);
1987 g_slist_free(uri_list);