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;
363 g_return_if_fail(mimeinfo != NULL);
364 text = GTK_TEXT_VIEW(textview->text);
365 buffer = gtk_text_view_get_buffer(text);
366 charcount = gtk_text_buffer_get_char_count(buffer);
367 gtk_text_buffer_get_end_iter(buffer, &iter);
369 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
371 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
374 fp = fopen(mimeinfo->filename, "rb");
375 fseek(fp, mimeinfo->offset, SEEK_SET);
376 headers = textview_scan_header(textview, fp);
379 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
380 textview_show_header(textview, headers);
381 procheader_header_array_destroy(headers);
387 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
389 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
391 g_snprintf(buf, sizeof(buf), "\n[%s %s/%s (%d bytes)]\n",
393 procmime_get_type_str(mimeinfo->type),
394 mimeinfo->subtype, mimeinfo->length);
396 g_snprintf(buf, sizeof(buf), "\n[%s/%s (%d bytes)]\n",
397 procmime_get_type_str(mimeinfo->type),
398 mimeinfo->subtype, mimeinfo->length);
400 if (mimeinfo->type != MIMETYPE_TEXT) {
401 gtk_text_buffer_insert(buffer, &iter, buf, -1);
402 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
403 if (prefs_common.display_header && (charcount > 0))
404 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
406 textview_write_body(textview, mimeinfo);
410 static void recursive_add_parts(TextView *textview, GNode *node)
415 mimeinfo = (MimeInfo *) node->data;
417 textview_add_part(textview, mimeinfo);
419 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
420 (mimeinfo->type != MIMETYPE_MESSAGE))
423 if (strcasecmp(mimeinfo->subtype, "alternative") == 0) {
424 GNode * prefered_body;
432 prefered_body = NULL;
435 for(iter = g_node_first_child(node) ; iter != NULL ;
436 iter = g_node_next_sibling(iter)) {
441 submime = (MimeInfo *) iter->data;
442 if (submime->type == MIMETYPE_TEXT)
445 if (submime->subtype != NULL) {
446 if (strcasecmp(submime->subtype, "plain") == 0)
450 if (score > prefered_score) {
451 prefered_score = score;
452 prefered_body = iter;
456 if (prefered_body != NULL) {
457 recursive_add_parts(textview, prefered_body);
461 for(iter = g_node_first_child(node) ; iter != NULL ;
462 iter = g_node_next_sibling(iter)) {
463 recursive_add_parts(textview, iter);
468 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
470 g_return_if_fail(mimeinfo != NULL);
472 recursive_add_parts(textview, mimeinfo->node);
475 #define TEXT_INSERT(str) \
476 gtk_text_buffer_insert(buffer, &iter, str, -1)
478 void textview_show_error(TextView *textview)
481 GtkTextBuffer *buffer;
484 textview_set_font(textview, NULL);
485 textview_clear(textview);
487 text = GTK_TEXT_VIEW(textview->text);
488 buffer = gtk_text_view_get_buffer(text);
489 gtk_text_buffer_get_start_iter(buffer, &iter);
491 TEXT_INSERT(_("This message can't be displayed.\n"));
495 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
498 GtkTextBuffer *buffer;
501 if (!partinfo) return;
503 textview_set_font(textview, NULL);
504 textview_clear(textview);
506 text = GTK_TEXT_VIEW(textview->text);
507 buffer = gtk_text_view_get_buffer(text);
508 gtk_text_buffer_get_start_iter(buffer, &iter);
510 TEXT_INSERT(_("The following can be performed on this part by "));
511 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
513 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
514 TEXT_INSERT(_(" To display as text select 'Display as text' "));
515 TEXT_INSERT(_("(Shortcut key: 't')\n"));
516 TEXT_INSERT(_(" To open with an external program select 'Open' "));
517 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
518 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
519 TEXT_INSERT(_("mouse button),\n"));
520 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
526 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
531 const gchar *charset;
533 if (textview->messageview->forced_charset)
534 charset = textview->messageview->forced_charset;
536 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
538 textview_set_font(textview, charset);
540 conv = conv_code_converter_new(charset);
542 textview->is_in_signature = FALSE;
544 procmime_decode_content(mimeinfo);
546 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
549 filename = procmime_get_tmp_file_name(mimeinfo);
550 if (procmime_get_part(filename, mimeinfo) == 0) {
551 tmpfp = fopen(filename, "rb");
552 textview_show_html(textview, tmpfp, conv);
557 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
560 filename = procmime_get_tmp_file_name(mimeinfo);
561 if (procmime_get_part(filename, mimeinfo) == 0) {
562 tmpfp = fopen(filename, "rb");
563 textview_show_ertf(textview, tmpfp, conv);
569 tmpfp = fopen(mimeinfo->filename, "rb");
570 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
571 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
572 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
573 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
574 textview_write_line(textview, buf, conv);
578 conv_code_converter_destroy(conv);
581 static void textview_show_html(TextView *textview, FILE *fp,
587 parser = html_parser_new(fp, conv);
588 g_return_if_fail(parser != NULL);
590 while ((str = html_parse(parser)) != NULL) {
591 if (parser->state == HTML_HREF) {
592 /* first time : get and copy the URL */
593 if (parser->href == NULL) {
594 /* ALF - the sylpheed html parser returns an empty string,
595 * if still inside an <a>, but already parsed past HREF */
596 str = strtok(str, " ");
598 parser->href = strdup(str);
599 /* the URL may (or not) be followed by the
601 str = strtok(NULL, "");
605 textview_write_link(textview, str, parser->href, NULL);
607 textview_write_line(textview, str, NULL);
609 html_parser_destroy(parser);
612 static void textview_show_ertf(TextView *textview, FILE *fp,
618 parser = ertf_parser_new(fp, conv);
619 g_return_if_fail(parser != NULL);
621 while ((str = ertf_parse(parser)) != NULL) {
622 textview_write_line(textview, str, NULL);
625 ertf_parser_destroy(parser);
628 /* get_uri_part() - retrieves a URI starting from scanpos.
629 Returns TRUE if succesful */
630 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
631 const gchar **bp, const gchar **ep)
635 g_return_val_if_fail(start != NULL, FALSE);
636 g_return_val_if_fail(scanpos != NULL, FALSE);
637 g_return_val_if_fail(bp != NULL, FALSE);
638 g_return_val_if_fail(ep != NULL, FALSE);
642 /* find end point of URI */
643 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
644 if (!isgraph(*(const guchar *)ep_) ||
645 !isascii(*(const guchar *)ep_) ||
646 strchr("()<>\"", *ep_))
650 /* no punctuation at end of string */
652 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
653 * should pass some URI type to this function and decide on that whether
654 * to perform punctuation stripping */
656 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
658 for (; ep_ - 1 > scanpos + 1 &&
659 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
670 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
672 return g_strndup(bp, ep - bp);
675 /* valid mail address characters */
676 #define IS_RFC822_CHAR(ch) \
681 !strchr("(),;<>\"", (ch)))
683 /* alphabet and number within 7bit ASCII */
684 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
685 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
687 static GHashTable *create_domain_tab(void)
689 static const gchar *toplvl_domains [] = {
691 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
692 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
693 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
694 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
695 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
696 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
697 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
698 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
699 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
700 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
701 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
702 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
703 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
704 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
705 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
706 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
707 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
708 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
709 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
710 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
711 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
712 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
713 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
714 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
715 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
716 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
719 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
721 g_return_val_if_fail(htab, NULL);
722 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
723 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
727 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
729 const gint MAX_LVL_DOM_NAME_LEN = 6;
730 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
731 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
734 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
737 for (p = buf; p < m && first < last; *p++ = *first++)
741 return g_hash_table_lookup(tab, buf) != NULL;
744 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
745 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
746 const gchar **bp, const gchar **ep)
748 /* more complex than the uri part because we need to scan back and forward starting from
749 * the scan position. */
750 gboolean result = FALSE;
751 const gchar *bp_ = NULL;
752 const gchar *ep_ = NULL;
753 static GHashTable *dom_tab;
754 const gchar *last_dot = NULL;
755 const gchar *prelast_dot = NULL;
756 const gchar *last_tld_char = NULL;
758 /* the informative part of the email address (describing the name
759 * of the email address owner) may contain quoted parts. the
760 * closure stack stores the last encountered quotes. */
761 gchar closure_stack[128];
762 gchar *ptr = closure_stack;
764 g_return_val_if_fail(start != NULL, FALSE);
765 g_return_val_if_fail(scanpos != NULL, FALSE);
766 g_return_val_if_fail(bp != NULL, FALSE);
767 g_return_val_if_fail(ep != NULL, FALSE);
770 dom_tab = create_domain_tab();
771 g_return_val_if_fail(dom_tab, FALSE);
773 /* scan start of address */
774 for (bp_ = scanpos - 1;
775 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
778 /* TODO: should start with an alnum? */
780 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
783 if (bp_ != scanpos) {
784 /* scan end of address */
785 for (ep_ = scanpos + 1;
786 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
788 prelast_dot = last_dot;
790 if (*(last_dot + 1) == '.') {
791 if (prelast_dot == NULL)
793 last_dot = prelast_dot;
798 /* TODO: really should terminate with an alnum? */
799 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
804 if (last_dot == NULL)
807 last_dot = prelast_dot;
808 if (last_dot == NULL || (scanpos + 1 >= last_dot))
812 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
813 if (*last_tld_char == '?')
816 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
823 if (!result) return FALSE;
825 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
826 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
829 /* see if this is <bracketed>; in this case we also scan for the informative part. */
830 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
833 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
834 #define IN_STACK() (ptr > closure_stack)
835 /* has underrun check */
836 #define POP_STACK() if(IN_STACK()) --ptr
837 /* has overrun check */
838 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
839 /* has underrun check */
840 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
844 /* scan for the informative part. */
845 for (bp_ -= 2; bp_ >= start; bp_--) {
846 /* if closure on the stack keep scanning */
847 if (PEEK_STACK() == *bp_) {
851 if (*bp_ == '\'' || *bp_ == '"') {
856 /* if nothing in the closure stack, do the special conditions
857 * the following if..else expression simply checks whether
858 * a token is acceptable. if not acceptable, the clause
859 * should terminate the loop with a 'break' */
862 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
863 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
864 /* hyphens are allowed, but only in
866 } else if (!ispunct(*bp_)) {
867 /* but anything not being a punctiation
870 break; /* anything else is rejected */
883 /* scan forward (should start with an alnum) */
884 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
894 #undef IS_ASCII_ALNUM
895 #undef IS_RFC822_CHAR
897 static gchar *make_email_string(const gchar *bp, const gchar *ep)
899 /* returns a mailto: URI; mailto: is also used to detect the
900 * uri type later on in the button_pressed signal handler */
904 tmp = g_strndup(bp, ep - bp);
905 result = g_strconcat("mailto:", tmp, NULL);
911 static gchar *make_http_string(const gchar *bp, const gchar *ep)
913 /* returns an http: URI; */
917 tmp = g_strndup(bp, ep - bp);
918 result = g_strconcat("http://", tmp, NULL);
924 #define ADD_TXT_POS(bp_, ep_, pti_) \
925 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
927 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
930 g_warning("alloc error scanning URIs\n"); \
931 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
937 /* textview_make_clickable_parts() - colorizes clickable parts */
938 static void textview_make_clickable_parts(TextView *textview,
940 const gchar *uri_tag,
941 const gchar *linebuf)
943 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
944 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
947 /* parse table - in order of priority */
949 const gchar *needle; /* token */
951 /* token search function */
952 gchar *(*search) (const gchar *haystack,
953 const gchar *needle);
954 /* part parsing function */
955 gboolean (*parse) (const gchar *start,
956 const gchar *scanpos,
959 /* part to URI function */
960 gchar *(*build_uri) (const gchar *bp,
964 static struct table parser[] = {
965 {"http://", strcasestr, get_uri_part, make_uri_string},
966 {"https://", strcasestr, get_uri_part, make_uri_string},
967 {"ftp://", strcasestr, get_uri_part, make_uri_string},
968 {"www.", strcasestr, get_uri_part, make_http_string},
969 {"mailto:", strcasestr, get_uri_part, make_uri_string},
970 {"@", strcasestr, get_email_part, make_email_string}
972 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
975 const gchar *walk, *bp, *ep;
978 const gchar *bp, *ep; /* text position */
979 gint pti; /* index in parse table */
980 struct txtpos *next; /* next */
981 } head = {NULL, NULL, 0, NULL}, *last = &head;
983 gtk_text_buffer_get_end_iter(buffer, &iter);
985 /* parse for clickable parts, and build a list of begin and end positions */
986 for (walk = linebuf, n = 0;;) {
987 gint last_index = PARSE_ELEMS;
988 gchar *scanpos = NULL;
990 /* FIXME: this looks phony. scanning for anything in the parse table */
991 for (n = 0; n < PARSE_ELEMS; n++) {
994 tmp = parser[n].search(walk, parser[n].needle);
996 if (scanpos == NULL || tmp < scanpos) {
1004 /* check if URI can be parsed */
1005 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1006 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1007 ADD_TXT_POS(bp, ep, last_index);
1011 strlen(parser[last_index].needle);
1016 /* colorize this line */
1018 const gchar *normal_text = linebuf;
1021 for (last = head.next; last != NULL;
1022 normal_text = last->ep, last = last->next) {
1025 uri = g_new(RemoteURI, 1);
1026 if (last->bp - normal_text > 0)
1027 gtk_text_buffer_insert_with_tags_by_name
1030 last->bp - normal_text,
1032 uri->uri = parser[last->pti].build_uri(last->bp,
1034 uri->start = gtk_text_iter_get_offset(&iter);
1035 gtk_text_buffer_insert_with_tags_by_name
1037 last->bp, last->ep - last->bp,
1039 uri->end = gtk_text_iter_get_offset(&iter);
1040 textview->uri_list =
1041 g_slist_append(textview->uri_list, uri);
1045 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1049 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1057 static void textview_write_line(TextView *textview, const gchar *str,
1058 CodeConverter *conv)
1061 GtkTextBuffer *buffer;
1063 gchar buf[BUFFSIZE];
1065 gint quotelevel = -1;
1066 gchar quote_tag_str[10];
1068 text = GTK_TEXT_VIEW(textview->text);
1069 buffer = gtk_text_view_get_buffer(text);
1070 gtk_text_buffer_get_end_iter(buffer, &iter);
1073 strncpy2(buf, str, sizeof(buf));
1074 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1075 strncpy2(buf, str, sizeof(buf));
1078 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1081 /* change color of quotation
1082 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1083 Up to 3 levels of quotations are detected, and each
1084 level is colored using a different color. */
1085 if (prefs_common.enable_color
1086 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1087 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1089 /* set up the correct foreground color */
1090 if (quotelevel > 2) {
1091 /* recycle colors */
1092 if (prefs_common.recycle_quote_colors)
1099 if (quotelevel == -1)
1102 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1103 "quote%d", quotelevel);
1104 fg_color = quote_tag_str;
1107 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1108 fg_color = "signature";
1109 textview->is_in_signature = TRUE;
1112 if (prefs_common.enable_color)
1113 textview_make_clickable_parts(textview, fg_color, "link", buf);
1115 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1118 void textview_write_link(TextView *textview, const gchar *str,
1119 const gchar *uri, CodeConverter *conv)
1121 GdkColor *link_color = NULL;
1123 GtkTextBuffer *buffer;
1125 gchar buf[BUFFSIZE];
1132 text = GTK_TEXT_VIEW(textview->text);
1133 buffer = gtk_text_view_get_buffer(text);
1134 gtk_text_buffer_get_end_iter(buffer, &iter);
1139 if (textview->text_is_mb)
1140 conv_localetodisp(buf, sizeof(buf), str);
1142 strncpy2(buf, str, sizeof(buf));
1143 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1144 conv_localetodisp(buf, sizeof(buf), str);
1145 else if (textview->text_is_mb)
1146 conv_unreadable_locale(buf);
1149 strncpy2(buf, str, sizeof(buf));
1150 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1151 conv_localetodisp(buf, sizeof(buf), str);
1156 gtk_text_buffer_get_end_iter(buffer, &iter);
1158 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1159 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1161 if (prefs_common.enable_color) {
1162 link_color = &uri_color;
1164 r_uri = g_new(RemoteURI, 1);
1165 r_uri->uri = g_strdup(uri);
1166 r_uri->start = gtk_text_iter_get_offset(&iter);
1167 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1169 r_uri->end = gtk_text_iter_get_offset(&iter);
1170 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1173 void textview_clear(TextView *textview)
1175 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1176 GtkTextBuffer *buffer;
1178 buffer = gtk_text_view_get_buffer(text);
1179 gtk_text_buffer_set_text(buffer, "\0", -1);
1181 TEXTVIEW_STATUSBAR_POP(textview);
1182 textview_uri_list_remove_all(textview->uri_list);
1183 textview->uri_list = NULL;
1185 textview->body_pos = 0;
1188 void textview_destroy(TextView *textview)
1190 textview_uri_list_remove_all(textview->uri_list);
1191 textview->uri_list = NULL;
1196 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1198 textview->show_all_headers = all_headers;
1201 void textview_set_font(TextView *textview, const gchar *codeset)
1203 if (prefs_common.textfont) {
1204 PangoFontDescription *font_desc = NULL;
1206 if (prefs_common.textfont)
1207 font_desc = pango_font_description_from_string
1208 (prefs_common.textfont);
1210 gtk_widget_modify_font(textview->text, font_desc);
1211 pango_font_description_free(font_desc);
1214 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1215 prefs_common.line_space / 2);
1216 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1217 prefs_common.line_space / 2);
1218 if (prefs_common.head_space) {
1219 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1221 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1225 void textview_set_text(TextView *textview, const gchar *text)
1228 GtkTextBuffer *buffer;
1230 g_return_if_fail(textview != NULL);
1231 g_return_if_fail(text != NULL);
1233 textview_clear(textview);
1235 view = GTK_TEXT_VIEW(textview->text);
1236 buffer = gtk_text_view_get_buffer(view);
1237 gtk_text_buffer_set_text(buffer, text, strlen(text));
1253 H_ORGANIZATION = 11,
1256 void textview_set_position(TextView *textview, gint pos)
1258 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1259 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1262 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1263 gtk_text_buffer_place_cursor(buffer, &iter);
1264 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1267 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1269 gchar buf[BUFFSIZE];
1270 GPtrArray *headers, *sorted_headers;
1271 GSList *disphdr_list;
1275 g_return_val_if_fail(fp != NULL, NULL);
1277 if (textview->show_all_headers)
1278 return procheader_get_header_array_asis(fp);
1280 if (!prefs_common.display_header) {
1281 while (fgets(buf, sizeof(buf), fp) != NULL)
1282 if (buf[0] == '\r' || buf[0] == '\n') break;
1286 headers = procheader_get_header_array_asis(fp);
1288 sorted_headers = g_ptr_array_new();
1290 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1291 disphdr_list = disphdr_list->next) {
1292 DisplayHeaderProp *dp =
1293 (DisplayHeaderProp *)disphdr_list->data;
1295 for (i = 0; i < headers->len; i++) {
1296 header = g_ptr_array_index(headers, i);
1298 if (procheader_headername_equal(header->name,
1301 procheader_header_free(header);
1303 g_ptr_array_add(sorted_headers, header);
1305 g_ptr_array_remove_index(headers, i);
1311 if (prefs_common.show_other_header) {
1312 for (i = 0; i < headers->len; i++) {
1313 header = g_ptr_array_index(headers, i);
1314 g_ptr_array_add(sorted_headers, header);
1316 g_ptr_array_free(headers, TRUE);
1318 procheader_header_array_destroy(headers);
1321 return sorted_headers;
1324 static void textview_show_header(TextView *textview, GPtrArray *headers)
1326 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1327 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1332 g_return_if_fail(headers != NULL);
1334 for (i = 0; i < headers->len; i++) {
1335 header = g_ptr_array_index(headers, i);
1336 g_return_if_fail(header->name != NULL);
1338 gtk_text_buffer_get_end_iter (buffer, &iter);
1339 gtk_text_buffer_insert_with_tags_by_name
1340 (buffer, &iter, header->name, -1,
1341 "header_title", "header", NULL);
1342 if (header->name[strlen(header->name) - 1] != ' ')
1343 gtk_text_buffer_insert_with_tags_by_name
1344 (buffer, &iter, " ", 1,
1345 "header_title", "header", NULL);
1347 if (procheader_headername_equal(header->name, "Subject") ||
1348 procheader_headername_equal(header->name, "From") ||
1349 procheader_headername_equal(header->name, "To") ||
1350 procheader_headername_equal(header->name, "Cc"))
1351 unfold_line(header->body);
1355 if (textview->text_is_mb == TRUE)
1356 conv_unreadable_locale(header->body);
1359 if (prefs_common.enable_color &&
1360 (procheader_headername_equal(header->name, "X-Mailer") ||
1361 procheader_headername_equal(header->name,
1363 strstr(header->body, "Sylpheed") != NULL) {
1364 gtk_text_buffer_get_end_iter (buffer, &iter);
1365 gtk_text_buffer_insert_with_tags_by_name
1366 (buffer, &iter, header->body, -1,
1367 "header", "emphasis", NULL);
1368 } else if (prefs_common.enable_color) {
1369 textview_make_clickable_parts(textview, "header", "link",
1372 textview_make_clickable_parts(textview, "header", NULL,
1375 gtk_text_buffer_get_end_iter (buffer, &iter);
1376 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1381 gboolean textview_search_string(TextView *textview, const gchar *str,
1384 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1385 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1387 GtkTextIter iter, start, end, real_end, *pos;
1388 gboolean found = FALSE;
1389 gint insert_offset, selbound_offset;
1391 /* reset selection */
1392 mark = gtk_text_buffer_get_mark(buffer, "insert");
1393 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1394 insert_offset = gtk_text_iter_get_offset(&start);
1395 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1396 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1397 selbound_offset = gtk_text_iter_get_offset(&end);
1399 pos = insert_offset > selbound_offset ? &start : &end;
1400 gtk_text_buffer_place_cursor(buffer, pos);
1403 mark = gtk_text_buffer_get_insert(buffer);
1404 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1406 found = gtk_text_iter_forward_search(&iter, str,
1407 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1408 &start, &end, NULL);
1412 gtk_text_buffer_get_end_iter(buffer, &real_end);
1413 text = strdup(gtk_text_buffer_get_text(buffer, &iter,
1416 while (!found && i++ < strlen(text) - 1) {
1417 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1420 i += gtk_text_iter_get_offset(&end);
1423 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1424 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1432 gtk_text_buffer_place_cursor(buffer, &start);
1433 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1435 mark = gtk_text_buffer_get_mark(buffer, "insert");
1436 gtk_text_view_scroll_mark_onscreen(text, mark);
1442 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1445 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1446 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1448 GtkTextIter iter, start, real_start, end, *pos;
1449 gboolean found = FALSE;
1450 gint insert_offset, selbound_offset;
1452 /* reset selection */
1453 mark = gtk_text_buffer_get_mark(buffer, "insert");
1454 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1455 insert_offset = gtk_text_iter_get_offset(&start);
1456 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1457 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1458 selbound_offset = gtk_text_iter_get_offset(&end);
1460 pos = insert_offset < selbound_offset ? &start : &end;
1461 gtk_text_buffer_place_cursor(buffer, pos);
1464 mark = gtk_text_buffer_get_insert(buffer);
1465 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1467 found = gtk_text_iter_backward_search(&iter, str,
1468 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1469 &start, &end, NULL);
1473 if (gtk_text_iter_get_offset(&iter) == 0)
1474 gtk_text_buffer_get_end_iter(buffer, &iter);
1476 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1477 gtk_text_buffer_get_start_iter(buffer, &real_start);
1479 text = strdup(gtk_text_buffer_get_text(buffer, &real_start,
1482 while (!found && i-- > 0) {
1483 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1487 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1488 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1496 gtk_text_buffer_place_cursor(buffer, &end);
1497 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1499 mark = gtk_text_buffer_get_mark(buffer, "insert");
1500 gtk_text_view_scroll_mark_onscreen(text, mark);
1506 void textview_scroll_one_line(TextView *textview, gboolean up)
1508 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1509 GtkAdjustment *vadj = text->vadjustment;
1512 if (prefs_common.enable_smooth_scroll) {
1513 textview_smooth_scroll_one_line(textview, up);
1518 upper = vadj->upper - vadj->page_size;
1519 if (vadj->value < upper) {
1521 vadj->step_increment * 4;
1523 MIN(vadj->value, upper);
1524 g_signal_emit_by_name(G_OBJECT(vadj),
1525 "value_changed", 0);
1528 if (vadj->value > 0.0) {
1530 vadj->step_increment * 4;
1532 MAX(vadj->value, 0.0);
1533 g_signal_emit_by_name(G_OBJECT(vadj),
1534 "value_changed", 0);
1539 gboolean textview_scroll_page(TextView *textview, gboolean up)
1541 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1542 GtkAdjustment *vadj = text->vadjustment;
1546 if (prefs_common.enable_smooth_scroll)
1547 return textview_smooth_scroll_page(textview, up);
1549 if (prefs_common.scroll_halfpage)
1550 page_incr = vadj->page_increment / 2;
1552 page_incr = vadj->page_increment;
1555 upper = vadj->upper - vadj->page_size;
1556 if (vadj->value < upper) {
1557 vadj->value += page_incr;
1558 vadj->value = MIN(vadj->value, upper);
1559 g_signal_emit_by_name(G_OBJECT(vadj),
1560 "value_changed", 0);
1564 if (vadj->value > 0.0) {
1565 vadj->value -= page_incr;
1566 vadj->value = MAX(vadj->value, 0.0);
1567 g_signal_emit_by_name(G_OBJECT(vadj),
1568 "value_changed", 0);
1576 static void textview_smooth_scroll_do(TextView *textview,
1577 gfloat old_value, gfloat last_value,
1580 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1581 GtkAdjustment *vadj = text->vadjustment;
1586 if (old_value < last_value) {
1587 change_value = last_value - old_value;
1590 change_value = old_value - last_value;
1594 for (i = step; i <= change_value; i += step) {
1595 vadj->value = old_value + (up ? -i : i);
1596 g_signal_emit_by_name(G_OBJECT(vadj),
1597 "value_changed", 0);
1600 vadj->value = last_value;
1601 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1604 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1606 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1607 GtkAdjustment *vadj = text->vadjustment;
1613 upper = vadj->upper - vadj->page_size;
1614 if (vadj->value < upper) {
1615 old_value = vadj->value;
1616 last_value = vadj->value +
1617 vadj->step_increment * 4;
1618 last_value = MIN(last_value, upper);
1620 textview_smooth_scroll_do(textview, old_value,
1622 prefs_common.scroll_step);
1625 if (vadj->value > 0.0) {
1626 old_value = vadj->value;
1627 last_value = vadj->value -
1628 vadj->step_increment * 4;
1629 last_value = MAX(last_value, 0.0);
1631 textview_smooth_scroll_do(textview, old_value,
1633 prefs_common.scroll_step);
1638 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1640 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1641 GtkAdjustment *vadj = text->vadjustment;
1647 if (prefs_common.scroll_halfpage)
1648 page_incr = vadj->page_increment / 2;
1650 page_incr = vadj->page_increment;
1653 upper = vadj->upper - vadj->page_size;
1654 if (vadj->value < upper) {
1655 old_value = vadj->value;
1656 last_value = vadj->value + page_incr;
1657 last_value = MIN(last_value, upper);
1659 textview_smooth_scroll_do(textview, old_value,
1661 prefs_common.scroll_step);
1665 if (vadj->value > 0.0) {
1666 old_value = vadj->value;
1667 last_value = vadj->value - page_incr;
1668 last_value = MAX(last_value, 0.0);
1670 textview_smooth_scroll_do(textview, old_value,
1672 prefs_common.scroll_step);
1680 #define KEY_PRESS_EVENT_STOP() \
1681 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1684 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1687 SummaryView *summaryview = NULL;
1688 MessageView *messageview = textview->messageview;
1690 if (!event) return FALSE;
1691 if (messageview->mainwin)
1692 summaryview = messageview->mainwin->summaryview;
1694 switch (event->keyval) {
1709 summary_pass_key_press_event(summaryview, event);
1711 textview_scroll_page(textview, FALSE);
1714 textview_scroll_page(textview, TRUE);
1717 textview_scroll_one_line(textview,
1718 (event->state & GDK_MOD1_MASK) != 0);
1722 summary_pass_key_press_event(summaryview, event);
1727 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1728 KEY_PRESS_EVENT_STOP();
1729 mimeview_pass_key_press_event(messageview->mimeview,
1733 /* possible fall through */
1736 event->window != messageview->mainwin->window->window) {
1737 GdkEventKey tmpev = *event;
1739 tmpev.window = messageview->mainwin->window->window;
1740 KEY_PRESS_EVENT_STOP();
1741 gtk_widget_event(messageview->mainwin->window,
1742 (GdkEvent *)&tmpev);
1750 static gint show_url_timeout_cb(gpointer data)
1752 TextView *textview = (TextView *)data;
1754 TEXTVIEW_STATUSBAR_POP(textview);
1755 textview->show_url_timeout_tag = 0;
1760 *\brief Check to see if a web URL has been disguised as a different
1761 * URL (possible with HTML email).
1763 *\param uri The uri to check
1765 *\param textview The TextView the URL is contained in
1767 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1768 * it anyway, otherwise FALSE
1770 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1773 gboolean retval = TRUE;
1775 if (g_strncasecmp(uri->uri, "http:", 5) &&
1776 g_strncasecmp(uri->uri, "https:", 6) &&
1777 g_strncasecmp(uri->uri, "www.", 4))
1780 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1783 if (clicked_str == NULL)
1786 if (strcmp(clicked_str, uri->uri) &&
1787 (!g_strncasecmp(clicked_str, "http:", 5) ||
1788 !g_strncasecmp(clicked_str, "https:", 6) ||
1789 !g_strncasecmp(clicked_str, "www.", 4))) {
1793 /* allow uri->uri == http://somewhere.com
1794 and clicked_str == somewhere.com */
1795 str = g_strconcat("http://", clicked_str, NULL);
1797 if (!g_strcasecmp(str, uri->uri))
1802 if (retval == FALSE) {
1806 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1807 "the apparent URL (%s). \n"
1809 uri->uri, clicked_str);
1810 resp = alertpanel_with_type(_("Warning"),
1814 NULL, NULL, ALERT_WARNING);
1816 if (resp == G_ALERTDEFAULT)
1819 g_free(clicked_str);
1823 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1824 GdkEvent *event, GtkTextIter *iter,
1827 GtkTextIter start_iter, end_iter;
1828 gint start_pos, end_pos;
1829 GdkEventButton *bevent;
1832 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
1833 && event->type != GDK_MOTION_NOTIFY)
1836 bevent = (GdkEventButton *) event;
1838 /* get start and end positions */
1840 if(!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) {
1841 debug_print("Can't find start.");
1844 start_pos = gtk_text_iter_get_offset(&start_iter);
1847 if(!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) {
1848 debug_print("Can't find end");
1851 end_pos = gtk_text_iter_get_offset(&end_iter);
1853 /* search current uri */
1854 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1855 RemoteURI *uri = (RemoteURI *)cur->data;
1857 if (start_pos != uri->start || end_pos != uri->end)
1860 trimmed_uri = trim_string(uri->uri, 60);
1861 /* hover or single click: display url in statusbar */
1863 if (event->type == GDK_MOTION_NOTIFY
1864 || (event->type == GDK_BUTTON_PRESS && bevent->button == 1)) {
1865 if (textview->messageview->mainwin) {
1866 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1867 textview->show_url_timeout_tag = gtk_timeout_add
1868 (4000, show_url_timeout_cb, textview);
1872 /* doubleclick: open compose / add address / browser */
1873 if ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) ||
1874 bevent->button == 2 || bevent->button == 3) {
1875 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1876 if (bevent->button == 3) {
1877 gchar *fromname, *fromaddress;
1880 fromaddress = g_strdup(uri->uri + 7);
1881 /* Hiroyuki: please put this function in utils.c! */
1882 fromname = procheader_get_fromname(fromaddress);
1883 extract_address(fromaddress);
1884 g_message("adding from textview %s <%s>", fromname, fromaddress);
1885 /* Add to address book - Match */
1886 addressbook_add_contact( fromname, fromaddress, NULL );
1888 g_free(fromaddress);
1891 PrefsAccount *account = NULL;
1893 if (textview->messageview && textview->messageview->msginfo &&
1894 textview->messageview->msginfo->folder) {
1895 FolderItem *folder_item;
1897 folder_item = textview->messageview->msginfo->folder;
1898 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1899 account = account_find_from_id(folder_item->prefs->default_account);
1901 compose_new(account, uri->uri + 7, NULL);
1905 if (textview_uri_security_check(textview, uri) == TRUE)
1907 prefs_common.uri_cmd);
1911 g_free(trimmed_uri);
1918 *\brief Check to see if a web URL has been disguised as a different
1919 * URL (possible with HTML email).
1921 *\param uri The uri to check
1923 *\param textview The TextView the URL is contained in
1925 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1926 * it anyway, otherwise FALSE
1928 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
1931 gboolean retval = TRUE;
1933 if (is_uri_string(uri->uri) == FALSE)
1936 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1937 uri->start, uri->end);
1938 if (visible_str == NULL)
1941 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
1943 gchar *visible_uri_path;
1945 uri_path = get_uri_path(uri->uri);
1946 visible_uri_path = get_uri_path(visible_str);
1947 if (strcmp(uri_path, visible_uri_path) != 0)
1951 if (retval == FALSE) {
1955 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1956 "the apparent URL (%s).\n"
1958 uri->uri, visible_str);
1959 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"), _("No"), NULL,
1960 NULL, ALERT_WARNING);
1962 if (aval == G_ALERTDEFAULT)
1966 g_free(visible_str);
1971 static void textview_uri_list_remove_all(GSList *uri_list)
1975 for (cur = uri_list; cur != NULL; cur = cur->next) {
1977 g_free(((RemoteURI *)cur->data)->uri);
1982 g_slist_free(uri_list);