2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2004 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtkvbox.h>
30 #include <gtk/gtkscrolledwindow.h>
31 #include <gtk/gtksignal.h>
39 #include "summaryview.h"
40 #include "procheader.h"
41 #include "prefs_common.h"
49 #include "addressbook.h"
50 #include "displayheader.h"
53 #include "alertpanel.h"
55 typedef struct _RemoteURI RemoteURI;
65 static GdkColor quote_colors[3] = {
66 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
67 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
68 {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
71 static GdkColor signature_color = {
78 static GdkColor uri_color = {
85 static GdkColor emphasis_color = {
93 static GdkColor error_color = {
102 #define TEXTVIEW_STATUSBAR_PUSH(textview, str) \
104 gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
105 textview->messageview->statusbar_cid, str); \
108 #define TEXTVIEW_STATUSBAR_POP(textview) \
110 gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
111 textview->messageview->statusbar_cid); \
114 static void textview_show_ertf (TextView *textview,
116 CodeConverter *conv);
117 static void textview_add_part (TextView *textview,
119 static void textview_add_parts (TextView *textview,
121 static void textview_write_body (TextView *textview,
123 const gchar *charset);
124 static void textview_show_html (TextView *textview,
126 CodeConverter *conv);
128 static void textview_write_line (TextView *textview,
130 CodeConverter *conv);
131 static void textview_write_link (TextView *textview,
134 CodeConverter *conv);
136 static GPtrArray *textview_scan_header (TextView *textview,
138 static void textview_show_header (TextView *textview,
141 static gint textview_key_pressed (GtkWidget *widget,
144 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
145 GdkEvent *event, GtkTextIter *iter,
147 static void textview_smooth_scroll_do (TextView *textview,
151 static void textview_smooth_scroll_one_line (TextView *textview,
153 static gboolean textview_smooth_scroll_page (TextView *textview,
156 static gboolean textview_uri_security_check (TextView *textview,
158 static void textview_uri_list_remove_all (GSList *uri_list);
161 static void populate_popup(GtkTextView *textview, GtkMenu *menu,
164 gtk_menu_detach(menu);
167 TextView *textview_create(void)
171 GtkWidget *scrolledwin;
173 GtkTextBuffer *buffer;
174 GtkClipboard *clipboard;
175 PangoFontDescription *font_desc = NULL;
177 debug_print("Creating text view...\n");
178 textview = g_new0(TextView, 1);
180 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
181 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
182 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
183 gtk_widget_set_size_request(scrolledwin, prefs_common.mainview_width, -1);
184 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
187 /* create GtkSText widgets for single-byte and multi-byte character */
188 text = gtk_text_view_new();
189 gtk_widget_show(text);
190 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
191 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
192 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
193 g_signal_connect(G_OBJECT(text), "populate-popup",
194 G_CALLBACK(populate_popup), NULL);
197 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
198 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
199 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
201 gtk_widget_ensure_style(text);
203 if (prefs_common.normalfont)
204 font_desc = pango_font_description_from_string
205 (prefs_common.normalfont);
207 gtk_widget_modify_font(text, font_desc);
209 pango_font_description_free(font_desc);
211 gtk_widget_ref(scrolledwin);
213 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
215 g_signal_connect(G_OBJECT(text), "key_press_event",
216 G_CALLBACK(textview_key_pressed),
219 gtk_widget_show(scrolledwin);
221 vbox = gtk_vbox_new(FALSE, 0);
222 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
224 gtk_widget_show(vbox);
226 textview->vbox = vbox;
227 textview->scrolledwin = scrolledwin;
228 textview->text = text;
229 textview->uri_list = NULL;
230 textview->body_pos = 0;
231 textview->show_all_headers = FALSE;
232 textview->last_buttonpress = GDK_NOTHING;
233 textview->show_url_msgid = 0;
238 static void textview_create_tags(GtkTextView *text, TextView *textview)
240 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
243 gtk_text_buffer_create_tag(buffer, "header",
244 "pixels-above-lines", 0,
245 "pixels-above-lines-set", TRUE,
246 "pixels-below-lines", 0,
247 "pixels-below-lines-set", TRUE,
249 "left-margin-set", TRUE,
251 gtk_text_buffer_create_tag(buffer, "header_title",
252 "font", prefs_common.boldfont,
254 gtk_text_buffer_create_tag(buffer, "quote0",
255 "foreground-gdk", "e_colors[0],
257 gtk_text_buffer_create_tag(buffer, "quote1",
258 "foreground-gdk", "e_colors[1],
260 gtk_text_buffer_create_tag(buffer, "quote2",
261 "foreground-gdk", "e_colors[2],
263 gtk_text_buffer_create_tag(buffer, "emphasis",
264 "foreground-gdk", &emphasis_color,
266 gtk_text_buffer_create_tag(buffer, "signature",
267 "foreground-gdk", &signature_color,
269 tag = gtk_text_buffer_create_tag(buffer, "link",
270 "foreground-gdk", &uri_color,
273 g_signal_connect(G_OBJECT(tag), "event",
274 G_CALLBACK(textview_uri_button_pressed), textview);
277 void textview_init(TextView *textview)
279 gtkut_widget_disable_theme_engine(textview->text);
280 textview_update_message_colors();
281 textview_set_all_headers(textview, FALSE);
282 textview_set_font(textview, NULL);
284 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
287 void textview_update_message_colors(void)
289 GdkColor black = {0, 0, 0, 0};
291 if (prefs_common.enable_color) {
292 /* grab the quote colors, converting from an int to a GdkColor */
293 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
295 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
297 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
299 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
301 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
304 quote_colors[0] = quote_colors[1] = quote_colors[2] =
305 uri_color = emphasis_color = signature_color = black;
309 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
313 const gchar *charset = NULL;
315 if ((fp = fopen(file, "rb")) == NULL) {
316 FILE_OP_ERROR(file, "fopen");
320 if (textview->messageview->forced_charset)
321 charset = textview->messageview->forced_charset;
322 else if (prefs_common.force_charset)
323 charset = prefs_common.force_charset;
325 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
327 textview_set_font(textview, charset);
328 textview_clear(textview);
332 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) perror("fseek");
333 headers = textview_scan_header(textview, fp);
335 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
336 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
339 textview_show_header(textview, headers);
340 procheader_header_array_destroy(headers);
342 gtk_text_buffer_get_end_iter(buffer, &iter);
343 textview->body_pos = gtk_text_iter_get_offset(&iter);
346 textview_add_parts(textview, mimeinfo);
350 textview_set_position(textview, 0);
353 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
356 const gchar *charset = NULL;
358 g_return_if_fail(mimeinfo != NULL);
359 g_return_if_fail(fp != NULL);
361 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
362 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
363 textview_clear(textview);
364 textview_add_parts(textview, mimeinfo);
368 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
371 headers = textview_scan_header(textview, fp);
373 if (textview->messageview->forced_charset)
374 charset = textview->messageview->forced_charset;
375 else if (prefs_common.force_charset)
376 charset = prefs_common.force_charset;
378 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
380 textview_set_font(textview, charset);
382 textview_clear(textview);
386 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
387 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
390 textview_show_header(textview, headers);
391 procheader_header_array_destroy(headers);
393 gtk_text_buffer_get_end_iter(buffer, &iter);
394 textview->body_pos = gtk_text_iter_get_offset(&iter);
395 if (!mimeinfo->main) {
396 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
400 if (mimeinfo->type == MIMETYPE_MULTIPART)
401 textview_add_parts(textview, mimeinfo);
403 textview_write_body(textview, mimeinfo, charset);
407 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
410 GtkTextBuffer *buffer;
413 const gchar *charset = NULL;
414 GPtrArray *headers = NULL;
418 g_return_if_fail(mimeinfo != NULL);
419 text = GTK_TEXT_VIEW(textview->text);
420 buffer = gtk_text_view_get_buffer(text);
421 charcount = gtk_text_buffer_get_char_count(buffer);
422 gtk_text_buffer_get_end_iter(buffer, &iter);
424 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
426 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
429 fp = fopen(mimeinfo->filename, "rb");
430 fseek(fp, mimeinfo->offset, SEEK_SET);
431 headers = textview_scan_header(textview, fp);
434 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
435 textview_show_header(textview, headers);
436 procheader_header_array_destroy(headers);
442 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
444 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
446 g_snprintf(buf, sizeof(buf), "\n[%s %s/%s (%d bytes)]\n",
448 procmime_get_type_str(mimeinfo->type),
449 mimeinfo->subtype, mimeinfo->length);
451 g_snprintf(buf, sizeof(buf), "\n[%s/%s (%d bytes)]\n",
452 procmime_get_type_str(mimeinfo->type),
453 mimeinfo->subtype, mimeinfo->length);
455 if (mimeinfo->type != MIMETYPE_TEXT) {
456 gtk_text_buffer_insert(buffer, &iter, buf, -1);
457 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
458 if (prefs_common.display_header && (charcount > 0))
459 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
460 if (textview->messageview->forced_charset)
461 charset = textview->messageview->forced_charset;
462 else if (prefs_common.force_charset)
463 charset = prefs_common.force_charset;
465 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
467 textview_write_body(textview, mimeinfo, charset);
472 static gboolean add_parts_func(GNode *node, gpointer data)
474 MimeInfo *mimeinfo = (MimeInfo *) node->data;
475 TextView *textview = (TextView *) data;
477 g_return_val_if_fail(mimeinfo != NULL, FALSE);
479 textview_add_part(textview, mimeinfo);
484 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
486 g_return_if_fail(mimeinfo != NULL);
488 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, add_parts_func, textview);
492 static void recursive_add_parts(TextView *textview, GNode *node)
497 mimeinfo = (MimeInfo *) node->data;
499 textview_add_part(textview, mimeinfo);
501 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
502 (mimeinfo->type != MIMETYPE_MESSAGE))
505 if (strcasecmp(mimeinfo->subtype, "alternative") == 0) {
506 GNode * prefered_body;
514 prefered_body = NULL;
517 for(iter = g_node_first_child(node) ; iter != NULL ;
518 iter = g_node_next_sibling(iter)) {
523 submime = (MimeInfo *) iter->data;
524 if (submime->type == MIMETYPE_TEXT)
527 if (submime->subtype != NULL) {
528 if (strcasecmp(submime->subtype, "plain") == 0)
532 if (score > prefered_score) {
533 prefered_score = score;
534 prefered_body = iter;
538 if (prefered_body != NULL) {
539 recursive_add_parts(textview, prefered_body);
543 for(iter = g_node_first_child(node) ; iter != NULL ;
544 iter = g_node_next_sibling(iter)) {
545 recursive_add_parts(textview, iter);
550 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
552 g_return_if_fail(mimeinfo != NULL);
554 recursive_add_parts(textview, mimeinfo->node);
557 #define TEXT_INSERT(str) \
558 gtk_text_buffer_insert(buffer, &iter, str, -1)
560 void textview_show_error(TextView *textview)
563 GtkTextBuffer *buffer;
566 textview_set_font(textview, NULL);
567 textview_clear(textview);
569 text = GTK_TEXT_VIEW(textview->text);
570 buffer = gtk_text_view_get_buffer(text);
571 gtk_text_buffer_get_start_iter(buffer, &iter);
573 TEXT_INSERT(_("This message can't be displayed.\n"));
577 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
580 GtkTextBuffer *buffer;
583 if (!partinfo) return;
585 textview_set_font(textview, NULL);
586 textview_clear(textview);
588 text = GTK_TEXT_VIEW(textview->text);
589 buffer = gtk_text_view_get_buffer(text);
590 gtk_text_buffer_get_start_iter(buffer, &iter);
592 TEXT_INSERT(_("The following can be performed on this part by "));
593 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
595 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
596 TEXT_INSERT(_(" To display as text select 'Display as text' "));
597 TEXT_INSERT(_("(Shortcut key: 't')\n"));
598 TEXT_INSERT(_(" To open with an external program select 'Open' "));
599 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
600 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
601 TEXT_INSERT(_("mouse button),\n"));
602 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
608 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
609 const gchar *charset)
615 conv = conv_code_converter_new(charset);
617 textview->is_in_signature = FALSE;
619 if(mimeinfo->encoding_type != ENC_BINARY &&
620 mimeinfo->encoding_type != ENC_7BIT &&
621 mimeinfo->encoding_type != ENC_8BIT)
622 procmime_decode_content(mimeinfo);
624 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
627 filename = procmime_get_tmp_file_name(mimeinfo);
628 if (procmime_get_part(filename, mimeinfo) == 0) {
629 tmpfp = fopen(filename, "rb");
630 textview_show_html(textview, tmpfp, conv);
635 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
638 filename = procmime_get_tmp_file_name(mimeinfo);
639 if (procmime_get_part(filename, mimeinfo) == 0) {
640 tmpfp = fopen(filename, "rb");
641 textview_show_ertf(textview, tmpfp, conv);
647 tmpfp = fopen(mimeinfo->filename, "rb");
648 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
649 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
650 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
651 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
652 textview_write_line(textview, buf, conv);
656 conv_code_converter_destroy(conv);
659 static void textview_show_html(TextView *textview, FILE *fp,
665 parser = html_parser_new(fp, conv);
666 g_return_if_fail(parser != NULL);
668 while ((str = html_parse(parser)) != NULL) {
669 if (parser->state == HTML_HREF) {
670 /* first time : get and copy the URL */
671 if (parser->href == NULL) {
672 /* ALF - the sylpheed html parser returns an empty string,
673 * if still inside an <a>, but already parsed past HREF */
674 str = strtok(str, " ");
676 parser->href = strdup(str);
677 /* the URL may (or not) be followed by the
679 str = strtok(NULL, "");
683 textview_write_link(textview, str, parser->href, NULL);
685 textview_write_line(textview, str, NULL);
687 html_parser_destroy(parser);
690 static void textview_show_ertf(TextView *textview, FILE *fp,
696 parser = ertf_parser_new(fp, conv);
697 g_return_if_fail(parser != NULL);
699 while ((str = ertf_parse(parser)) != NULL) {
700 textview_write_line(textview, str, NULL);
703 ertf_parser_destroy(parser);
706 /* get_uri_part() - retrieves a URI starting from scanpos.
707 Returns TRUE if succesful */
708 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
709 const gchar **bp, const gchar **ep)
713 g_return_val_if_fail(start != NULL, FALSE);
714 g_return_val_if_fail(scanpos != NULL, FALSE);
715 g_return_val_if_fail(bp != NULL, FALSE);
716 g_return_val_if_fail(ep != NULL, FALSE);
720 /* find end point of URI */
721 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
722 if (!isgraph(*(const guchar *)ep_) ||
723 !isascii(*(const guchar *)ep_) ||
724 strchr("()<>\"", *ep_))
728 /* no punctuation at end of string */
730 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
731 * should pass some URI type to this function and decide on that whether
732 * to perform punctuation stripping */
734 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
736 for (; ep_ - 1 > scanpos + 1 &&
737 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
748 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
750 return g_strndup(bp, ep - bp);
753 /* valid mail address characters */
754 #define IS_RFC822_CHAR(ch) \
759 !strchr("(),;<>\"", (ch)))
761 /* alphabet and number within 7bit ASCII */
762 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
763 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
765 static GHashTable *create_domain_tab(void)
767 static const gchar *toplvl_domains [] = {
769 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
770 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
771 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
772 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
773 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
774 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
775 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
776 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
777 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
778 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
779 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
780 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
781 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
782 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
783 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
784 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
785 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
786 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
787 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
788 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
789 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
790 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
791 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
792 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
793 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
794 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
797 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
799 g_return_val_if_fail(htab, NULL);
800 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
801 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
805 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
807 const gint MAX_LVL_DOM_NAME_LEN = 6;
808 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
809 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
812 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
815 for (p = buf; p < m && first < last; *p++ = *first++)
819 return g_hash_table_lookup(tab, buf) != NULL;
822 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
823 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
824 const gchar **bp, const gchar **ep)
826 /* more complex than the uri part because we need to scan back and forward starting from
827 * the scan position. */
828 gboolean result = FALSE;
829 const gchar *bp_ = NULL;
830 const gchar *ep_ = NULL;
831 static GHashTable *dom_tab;
832 const gchar *last_dot = NULL;
833 const gchar *prelast_dot = NULL;
834 const gchar *last_tld_char = NULL;
836 /* the informative part of the email address (describing the name
837 * of the email address owner) may contain quoted parts. the
838 * closure stack stores the last encountered quotes. */
839 gchar closure_stack[128];
840 gchar *ptr = closure_stack;
842 g_return_val_if_fail(start != NULL, FALSE);
843 g_return_val_if_fail(scanpos != NULL, FALSE);
844 g_return_val_if_fail(bp != NULL, FALSE);
845 g_return_val_if_fail(ep != NULL, FALSE);
848 dom_tab = create_domain_tab();
849 g_return_val_if_fail(dom_tab, FALSE);
851 /* scan start of address */
852 for (bp_ = scanpos - 1;
853 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
856 /* TODO: should start with an alnum? */
858 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
861 if (bp_ != scanpos) {
862 /* scan end of address */
863 for (ep_ = scanpos + 1;
864 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
866 prelast_dot = last_dot;
868 if (*(last_dot + 1) == '.') {
869 if (prelast_dot == NULL)
871 last_dot = prelast_dot;
876 /* TODO: really should terminate with an alnum? */
877 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
882 if (last_dot == NULL)
885 last_dot = prelast_dot;
886 if (last_dot == NULL || (scanpos + 1 >= last_dot))
890 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
891 if (*last_tld_char == '?')
894 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
901 if (!result) return FALSE;
903 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
904 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
907 /* see if this is <bracketed>; in this case we also scan for the informative part. */
908 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
911 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
912 #define IN_STACK() (ptr > closure_stack)
913 /* has underrun check */
914 #define POP_STACK() if(IN_STACK()) --ptr
915 /* has overrun check */
916 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
917 /* has underrun check */
918 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
922 /* scan for the informative part. */
923 for (bp_ -= 2; bp_ >= start; bp_--) {
924 /* if closure on the stack keep scanning */
925 if (PEEK_STACK() == *bp_) {
929 if (*bp_ == '\'' || *bp_ == '"') {
934 /* if nothing in the closure stack, do the special conditions
935 * the following if..else expression simply checks whether
936 * a token is acceptable. if not acceptable, the clause
937 * should terminate the loop with a 'break' */
940 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
941 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
942 /* hyphens are allowed, but only in
944 } else if (!ispunct(*bp_)) {
945 /* but anything not being a punctiation
948 break; /* anything else is rejected */
961 /* scan forward (should start with an alnum) */
962 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
972 #undef IS_ASCII_ALNUM
973 #undef IS_RFC822_CHAR
975 static gchar *make_email_string(const gchar *bp, const gchar *ep)
977 /* returns a mailto: URI; mailto: is also used to detect the
978 * uri type later on in the button_pressed signal handler */
982 tmp = g_strndup(bp, ep - bp);
983 result = g_strconcat("mailto:", tmp, NULL);
989 static gchar *make_http_string(const gchar *bp, const gchar *ep)
991 /* returns an http: URI; */
995 tmp = g_strndup(bp, ep - bp);
996 result = g_strconcat("http://", tmp, NULL);
1002 #define ADD_TXT_POS(bp_, ep_, pti_) \
1003 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1004 last = last->next; \
1005 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1006 last->next = NULL; \
1008 g_warning("alloc error scanning URIs\n"); \
1009 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1015 /* textview_make_clickable_parts() - colorizes clickable parts */
1016 static void textview_make_clickable_parts(TextView *textview,
1017 const gchar *fg_tag,
1018 const gchar *uri_tag,
1019 const gchar *linebuf)
1021 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1022 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1025 /* parse table - in order of priority */
1027 const gchar *needle; /* token */
1029 /* token search function */
1030 gchar *(*search) (const gchar *haystack,
1031 const gchar *needle);
1032 /* part parsing function */
1033 gboolean (*parse) (const gchar *start,
1034 const gchar *scanpos,
1037 /* part to URI function */
1038 gchar *(*build_uri) (const gchar *bp,
1042 static struct table parser[] = {
1043 {"http://", strcasestr, get_uri_part, make_uri_string},
1044 {"https://", strcasestr, get_uri_part, make_uri_string},
1045 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1046 {"www.", strcasestr, get_uri_part, make_http_string},
1047 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1048 {"@", strcasestr, get_email_part, make_email_string}
1050 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1053 const gchar *walk, *bp, *ep;
1056 const gchar *bp, *ep; /* text position */
1057 gint pti; /* index in parse table */
1058 struct txtpos *next; /* next */
1059 } head = {NULL, NULL, 0, NULL}, *last = &head;
1061 gtk_text_buffer_get_end_iter(buffer, &iter);
1063 /* parse for clickable parts, and build a list of begin and end positions */
1064 for (walk = linebuf, n = 0;;) {
1065 gint last_index = PARSE_ELEMS;
1066 gchar *scanpos = NULL;
1068 /* FIXME: this looks phony. scanning for anything in the parse table */
1069 for (n = 0; n < PARSE_ELEMS; n++) {
1072 tmp = parser[n].search(walk, parser[n].needle);
1074 if (scanpos == NULL || tmp < scanpos) {
1082 /* check if URI can be parsed */
1083 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1084 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1085 ADD_TXT_POS(bp, ep, last_index);
1089 strlen(parser[last_index].needle);
1094 /* colorize this line */
1096 const gchar *normal_text = linebuf;
1099 for (last = head.next; last != NULL;
1100 normal_text = last->ep, last = last->next) {
1103 uri = g_new(RemoteURI, 1);
1104 if (last->bp - normal_text > 0)
1105 gtk_text_buffer_insert_with_tags_by_name
1108 last->bp - normal_text,
1110 uri->uri = parser[last->pti].build_uri(last->bp,
1112 uri->start = gtk_text_iter_get_offset(&iter);
1113 gtk_text_buffer_insert_with_tags_by_name
1115 last->bp, last->ep - last->bp,
1117 uri->end = gtk_text_iter_get_offset(&iter);
1118 textview->uri_list =
1119 g_slist_append(textview->uri_list, uri);
1123 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1127 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1135 static void textview_write_line(TextView *textview, const gchar *str,
1136 CodeConverter *conv)
1139 GtkTextBuffer *buffer;
1141 gchar buf[BUFFSIZE];
1143 gint quotelevel = -1;
1144 gchar quote_tag_str[10];
1146 text = GTK_TEXT_VIEW(textview->text);
1147 buffer = gtk_text_view_get_buffer(text);
1148 gtk_text_buffer_get_end_iter(buffer, &iter);
1153 if (textview->text_is_mb)
1154 conv_localetodisp(buf, sizeof(buf), str);
1156 strncpy2(buf, str, sizeof(buf));
1157 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1158 conv_localetodisp(buf, sizeof(buf), str);
1159 else if (textview->text_is_mb)
1160 conv_unreadable_locale(buf);
1163 strncpy2(buf, str, sizeof(buf));
1164 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1165 conv_localetodisp(buf, sizeof(buf), str);
1169 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1172 /* change color of quotation
1173 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1174 Up to 3 levels of quotations are detected, and each
1175 level is colored using a different color. */
1176 if (prefs_common.enable_color
1177 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1178 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1180 /* set up the correct foreground color */
1181 if (quotelevel > 2) {
1182 /* recycle colors */
1183 if (prefs_common.recycle_quote_colors)
1190 if (quotelevel == -1)
1193 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1194 "quote%d", quotelevel);
1195 fg_color = quote_tag_str;
1198 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1199 fg_color = "signature";
1200 textview->is_in_signature = TRUE;
1203 if (prefs_common.enable_color)
1204 textview_make_clickable_parts(textview, fg_color, "link", buf);
1206 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1209 void textview_write_link(TextView *textview, const gchar *str,
1210 const gchar *uri, CodeConverter *conv)
1212 GdkColor *link_color = NULL;
1214 GtkTextBuffer *buffer;
1216 gchar buf[BUFFSIZE];
1223 text = GTK_TEXT_VIEW(textview->text);
1224 buffer = gtk_text_view_get_buffer(text);
1225 gtk_text_buffer_get_end_iter(buffer, &iter);
1230 if (textview->text_is_mb)
1231 conv_localetodisp(buf, sizeof(buf), str);
1233 strncpy2(buf, str, sizeof(buf));
1234 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1235 conv_localetodisp(buf, sizeof(buf), str);
1236 else if (textview->text_is_mb)
1237 conv_unreadable_locale(buf);
1240 strncpy2(buf, str, sizeof(buf));
1241 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1242 conv_localetodisp(buf, sizeof(buf), str);
1247 gtk_text_buffer_get_end_iter(buffer, &iter);
1249 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1250 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1252 if (prefs_common.enable_color) {
1253 link_color = &uri_color;
1255 r_uri = g_new(RemoteURI, 1);
1256 r_uri->uri = g_strdup(uri);
1257 r_uri->start = gtk_text_iter_get_offset(&iter);
1258 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1260 r_uri->end = gtk_text_iter_get_offset(&iter);
1261 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1264 void textview_clear(TextView *textview)
1266 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1267 GtkTextBuffer *buffer;
1269 buffer = gtk_text_view_get_buffer(text);
1270 gtk_text_buffer_set_text(buffer, "\0", -1);
1272 TEXTVIEW_STATUSBAR_POP(textview);
1273 textview_uri_list_remove_all(textview->uri_list);
1274 textview->uri_list = NULL;
1276 textview->body_pos = 0;
1279 void textview_destroy(TextView *textview)
1281 textview_uri_list_remove_all(textview->uri_list);
1282 textview->uri_list = NULL;
1287 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1289 textview->show_all_headers = all_headers;
1292 void textview_set_font(TextView *textview, const gchar *codeset)
1294 if (prefs_common.textfont) {
1295 PangoFontDescription *font_desc = NULL;
1297 if (prefs_common.textfont)
1298 font_desc = pango_font_description_from_string
1299 (prefs_common.textfont);
1301 gtk_widget_modify_font(textview->text, font_desc);
1302 pango_font_description_free(font_desc);
1305 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1306 prefs_common.line_space / 2);
1307 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1308 prefs_common.line_space / 2);
1309 if (prefs_common.head_space) {
1310 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1312 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1316 void textview_set_text(TextView *textview, const gchar *text)
1319 GtkTextBuffer *buffer;
1321 g_return_if_fail(textview != NULL);
1322 g_return_if_fail(text != NULL);
1324 textview_clear(textview);
1326 view = GTK_TEXT_VIEW(textview->text);
1327 buffer = gtk_text_view_get_buffer(view);
1328 gtk_text_buffer_set_text(buffer, text, strlen(text));
1344 H_ORGANIZATION = 11,
1347 void textview_set_position(TextView *textview, gint pos)
1349 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1350 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1353 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1354 gtk_text_buffer_place_cursor(buffer, &iter);
1355 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1358 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1360 gchar buf[BUFFSIZE];
1361 GPtrArray *headers, *sorted_headers;
1362 GSList *disphdr_list;
1366 g_return_val_if_fail(fp != NULL, NULL);
1368 if (textview->show_all_headers)
1369 return procheader_get_header_array_asis(fp);
1371 if (!prefs_common.display_header) {
1372 while (fgets(buf, sizeof(buf), fp) != NULL)
1373 if (buf[0] == '\r' || buf[0] == '\n') break;
1377 headers = procheader_get_header_array_asis(fp);
1379 sorted_headers = g_ptr_array_new();
1381 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1382 disphdr_list = disphdr_list->next) {
1383 DisplayHeaderProp *dp =
1384 (DisplayHeaderProp *)disphdr_list->data;
1386 for (i = 0; i < headers->len; i++) {
1387 header = g_ptr_array_index(headers, i);
1389 if (procheader_headername_equal(header->name,
1392 procheader_header_free(header);
1394 g_ptr_array_add(sorted_headers, header);
1396 g_ptr_array_remove_index(headers, i);
1402 if (prefs_common.show_other_header) {
1403 for (i = 0; i < headers->len; i++) {
1404 header = g_ptr_array_index(headers, i);
1405 g_ptr_array_add(sorted_headers, header);
1407 g_ptr_array_free(headers, TRUE);
1409 procheader_header_array_destroy(headers);
1412 return sorted_headers;
1415 static void textview_show_header(TextView *textview, GPtrArray *headers)
1417 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1418 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1423 g_return_if_fail(headers != NULL);
1425 for (i = 0; i < headers->len; i++) {
1426 header = g_ptr_array_index(headers, i);
1427 g_return_if_fail(header->name != NULL);
1429 gtk_text_buffer_get_end_iter (buffer, &iter);
1430 gtk_text_buffer_insert_with_tags_by_name
1431 (buffer, &iter, header->name, -1,
1432 "header_title", "header", NULL);
1433 if (header->name[strlen(header->name) - 1] != ' ')
1434 gtk_text_buffer_insert_with_tags_by_name
1435 (buffer, &iter, " ", 1,
1436 "header_title", "header", NULL);
1438 if (procheader_headername_equal(header->name, "Subject") ||
1439 procheader_headername_equal(header->name, "From") ||
1440 procheader_headername_equal(header->name, "To") ||
1441 procheader_headername_equal(header->name, "Cc"))
1442 unfold_line(header->body);
1446 if (textview->text_is_mb == TRUE)
1447 conv_unreadable_locale(header->body);
1450 if (prefs_common.enable_color &&
1451 (procheader_headername_equal(header->name, "X-Mailer") ||
1452 procheader_headername_equal(header->name,
1454 strstr(header->body, "Sylpheed") != NULL) {
1455 gtk_text_buffer_get_end_iter (buffer, &iter);
1456 gtk_text_buffer_insert_with_tags_by_name
1457 (buffer, &iter, header->body, -1,
1458 "header", "emphasis", NULL);
1459 } else if (prefs_common.enable_color) {
1460 textview_make_clickable_parts(textview, "header", "link",
1463 textview_make_clickable_parts(textview, "header", NULL,
1466 gtk_text_buffer_get_end_iter (buffer, &iter);
1467 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1472 gboolean textview_search_string(TextView *textview, const gchar *str,
1475 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1476 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1478 GtkTextIter iter, start, end, real_end, *pos;
1479 gboolean found = FALSE;
1480 gint insert_offset, selbound_offset;
1482 /* reset selection */
1483 mark = gtk_text_buffer_get_mark(buffer, "insert");
1484 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1485 insert_offset = gtk_text_iter_get_offset(&start);
1486 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1487 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1488 selbound_offset = gtk_text_iter_get_offset(&end);
1490 pos = insert_offset > selbound_offset ? &start : &end;
1491 gtk_text_buffer_place_cursor(buffer, pos);
1494 mark = gtk_text_buffer_get_insert(buffer);
1495 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1497 found = gtk_text_iter_forward_search(&iter, str,
1498 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1499 &start, &end, NULL);
1503 gtk_text_buffer_get_end_iter(buffer, &real_end);
1504 text = strdup(gtk_text_buffer_get_text(buffer, &iter,
1507 while (!found && i++ < strlen(text) - 1) {
1508 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1511 i += gtk_text_iter_get_offset(&end);
1514 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1515 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1523 gtk_text_buffer_place_cursor(buffer, &start);
1524 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1526 mark = gtk_text_buffer_get_mark(buffer, "insert");
1527 gtk_text_view_scroll_mark_onscreen(text, mark);
1533 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1536 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1537 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1539 GtkTextIter iter, start, real_start, end, *pos;
1540 gboolean found = FALSE;
1541 gint insert_offset, selbound_offset;
1543 /* reset selection */
1544 mark = gtk_text_buffer_get_mark(buffer, "insert");
1545 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1546 insert_offset = gtk_text_iter_get_offset(&start);
1547 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1548 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1549 selbound_offset = gtk_text_iter_get_offset(&end);
1551 pos = insert_offset < selbound_offset ? &start : &end;
1552 gtk_text_buffer_place_cursor(buffer, pos);
1555 mark = gtk_text_buffer_get_insert(buffer);
1556 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1558 found = gtk_text_iter_backward_search(&iter, str,
1559 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1560 &start, &end, NULL);
1564 if (gtk_text_iter_get_offset(&iter) == 0)
1565 gtk_text_buffer_get_end_iter(buffer, &iter);
1567 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1568 gtk_text_buffer_get_start_iter(buffer, &real_start);
1570 text = strdup(gtk_text_buffer_get_text(buffer, &real_start,
1573 while (!found && i-- > 0) {
1574 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1578 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1579 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1587 gtk_text_buffer_place_cursor(buffer, &end);
1588 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1590 mark = gtk_text_buffer_get_mark(buffer, "insert");
1591 gtk_text_view_scroll_mark_onscreen(text, mark);
1597 void textview_scroll_one_line(TextView *textview, gboolean up)
1599 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1600 GtkAdjustment *vadj = text->vadjustment;
1603 if (prefs_common.enable_smooth_scroll) {
1604 textview_smooth_scroll_one_line(textview, up);
1609 upper = vadj->upper - vadj->page_size;
1610 if (vadj->value < upper) {
1612 vadj->step_increment * 4;
1614 MIN(vadj->value, upper);
1615 g_signal_emit_by_name(G_OBJECT(vadj),
1616 "value_changed", 0);
1619 if (vadj->value > 0.0) {
1621 vadj->step_increment * 4;
1623 MAX(vadj->value, 0.0);
1624 g_signal_emit_by_name(G_OBJECT(vadj),
1625 "value_changed", 0);
1630 gboolean textview_scroll_page(TextView *textview, gboolean up)
1632 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1633 GtkAdjustment *vadj = text->vadjustment;
1637 if (prefs_common.enable_smooth_scroll)
1638 return textview_smooth_scroll_page(textview, up);
1640 if (prefs_common.scroll_halfpage)
1641 page_incr = vadj->page_increment / 2;
1643 page_incr = vadj->page_increment;
1646 upper = vadj->upper - vadj->page_size;
1647 if (vadj->value < upper) {
1648 vadj->value += page_incr;
1649 vadj->value = MIN(vadj->value, upper);
1650 g_signal_emit_by_name(G_OBJECT(vadj),
1651 "value_changed", 0);
1655 if (vadj->value > 0.0) {
1656 vadj->value -= page_incr;
1657 vadj->value = MAX(vadj->value, 0.0);
1658 g_signal_emit_by_name(G_OBJECT(vadj),
1659 "value_changed", 0);
1667 static void textview_smooth_scroll_do(TextView *textview,
1668 gfloat old_value, gfloat last_value,
1671 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1672 GtkAdjustment *vadj = text->vadjustment;
1677 if (old_value < last_value) {
1678 change_value = last_value - old_value;
1681 change_value = old_value - last_value;
1685 for (i = step; i <= change_value; i += step) {
1686 vadj->value = old_value + (up ? -i : i);
1687 g_signal_emit_by_name(G_OBJECT(vadj),
1688 "value_changed", 0);
1691 vadj->value = last_value;
1692 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1695 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1697 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1698 GtkAdjustment *vadj = text->vadjustment;
1704 upper = vadj->upper - vadj->page_size;
1705 if (vadj->value < upper) {
1706 old_value = vadj->value;
1707 last_value = vadj->value +
1708 vadj->step_increment * 4;
1709 last_value = MIN(last_value, upper);
1711 textview_smooth_scroll_do(textview, old_value,
1713 prefs_common.scroll_step);
1716 if (vadj->value > 0.0) {
1717 old_value = vadj->value;
1718 last_value = vadj->value -
1719 vadj->step_increment * 4;
1720 last_value = MAX(last_value, 0.0);
1722 textview_smooth_scroll_do(textview, old_value,
1724 prefs_common.scroll_step);
1729 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1731 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1732 GtkAdjustment *vadj = text->vadjustment;
1738 if (prefs_common.scroll_halfpage)
1739 page_incr = vadj->page_increment / 2;
1741 page_incr = vadj->page_increment;
1744 upper = vadj->upper - vadj->page_size;
1745 if (vadj->value < upper) {
1746 old_value = vadj->value;
1747 last_value = vadj->value + page_incr;
1748 last_value = MIN(last_value, upper);
1750 textview_smooth_scroll_do(textview, old_value,
1752 prefs_common.scroll_step);
1756 if (vadj->value > 0.0) {
1757 old_value = vadj->value;
1758 last_value = vadj->value - page_incr;
1759 last_value = MAX(last_value, 0.0);
1761 textview_smooth_scroll_do(textview, old_value,
1763 prefs_common.scroll_step);
1771 #define KEY_PRESS_EVENT_STOP() \
1772 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1775 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1778 SummaryView *summaryview = NULL;
1779 MessageView *messageview = textview->messageview;
1781 if (!event) return FALSE;
1782 if (messageview->mainwin)
1783 summaryview = messageview->mainwin->summaryview;
1785 switch (event->keyval) {
1800 summary_pass_key_press_event(summaryview, event);
1802 textview_scroll_page(textview, FALSE);
1805 textview_scroll_page(textview, TRUE);
1808 textview_scroll_one_line(textview,
1809 (event->state & GDK_MOD1_MASK) != 0);
1813 summary_pass_key_press_event(summaryview, event);
1818 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1819 KEY_PRESS_EVENT_STOP();
1820 mimeview_pass_key_press_event(messageview->mimeview,
1824 /* possible fall through */
1827 event->window != messageview->mainwin->window->window) {
1828 GdkEventKey tmpev = *event;
1830 tmpev.window = messageview->mainwin->window->window;
1831 KEY_PRESS_EVENT_STOP();
1832 gtk_widget_event(messageview->mainwin->window,
1833 (GdkEvent *)&tmpev);
1841 static gint show_url_timeout_cb(gpointer data)
1843 TextView *textview = (TextView *)data;
1845 TEXTVIEW_STATUSBAR_POP(textview);
1846 textview->show_url_timeout_tag = 0;
1851 *\brief Check to see if a web URL has been disguised as a different
1852 * URL (possible with HTML email).
1854 *\param uri The uri to check
1856 *\param textview The TextView the URL is contained in
1858 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1859 * it anyway, otherwise FALSE
1861 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1864 gboolean retval = TRUE;
1866 if (g_strncasecmp(uri->uri, "http:", 5) &&
1867 g_strncasecmp(uri->uri, "https:", 6) &&
1868 g_strncasecmp(uri->uri, "www.", 4))
1871 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1874 if (clicked_str == NULL)
1877 if (strcmp(clicked_str, uri->uri) &&
1878 (!g_strncasecmp(clicked_str, "http:", 5) ||
1879 !g_strncasecmp(clicked_str, "https:", 6) ||
1880 !g_strncasecmp(clicked_str, "www.", 4))) {
1884 /* allow uri->uri == http://somewhere.com
1885 and clicked_str == somewhere.com */
1886 str = g_strconcat("http://", clicked_str, NULL);
1888 if (!g_strcasecmp(str, uri->uri))
1893 if (retval == FALSE) {
1897 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1898 "the apparent URL (%s). \n"
1900 uri->uri, clicked_str);
1901 resp = alertpanel_with_type(_("Warning"),
1905 NULL, NULL, ALERT_WARNING);
1907 if (resp == G_ALERTDEFAULT)
1910 g_free(clicked_str);
1914 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1915 GdkEvent *event, GtkTextIter *iter,
1918 GtkTextIter start_iter, end_iter;
1919 gint start_pos, end_pos;
1920 GdkEventButton *bevent;
1923 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
1924 && event->type != GDK_MOTION_NOTIFY)
1927 bevent = (GdkEventButton *) event;
1929 /* get start and end positions */
1931 if(!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) {
1932 debug_print("Can't find start.");
1935 start_pos = gtk_text_iter_get_offset(&start_iter);
1938 if(!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) {
1939 debug_print("Can't find end");
1942 end_pos = gtk_text_iter_get_offset(&end_iter);
1944 /* search current uri */
1945 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1946 RemoteURI *uri = (RemoteURI *)cur->data;
1948 if (start_pos != uri->start || end_pos != uri->end)
1951 trimmed_uri = trim_string(uri->uri, 60);
1952 /* hover or single click: display url in statusbar */
1954 if (event->type == GDK_MOTION_NOTIFY
1955 || (event->type == GDK_BUTTON_PRESS && bevent->button == 1)) {
1956 if (textview->messageview->mainwin) {
1957 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1958 textview->show_url_timeout_tag = gtk_timeout_add
1959 (4000, show_url_timeout_cb, textview);
1963 /* doubleclick: open compose / add address / browser */
1964 if ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) ||
1965 bevent->button == 2 || bevent->button == 3) {
1966 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1967 if (bevent->button == 3) {
1968 gchar *fromname, *fromaddress;
1971 fromaddress = g_strdup(uri->uri + 7);
1972 /* Hiroyuki: please put this function in utils.c! */
1973 fromname = procheader_get_fromname(fromaddress);
1974 extract_address(fromaddress);
1975 g_message("adding from textview %s <%s>", fromname, fromaddress);
1976 /* Add to address book - Match */
1977 addressbook_add_contact( fromname, fromaddress, NULL );
1979 g_free(fromaddress);
1982 PrefsAccount *account = NULL;
1984 if (textview->messageview && textview->messageview->msginfo &&
1985 textview->messageview->msginfo->folder) {
1986 FolderItem *folder_item;
1988 folder_item = textview->messageview->msginfo->folder;
1989 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1990 account = account_find_from_id(folder_item->prefs->default_account);
1992 compose_new(account, uri->uri + 7, NULL);
1996 if (textview_uri_security_check(textview, uri) == TRUE)
1998 prefs_common.uri_cmd);
2002 g_free(trimmed_uri);
2009 *\brief Check to see if a web URL has been disguised as a different
2010 * URL (possible with HTML email).
2012 *\param uri The uri to check
2014 *\param textview The TextView the URL is contained in
2016 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2017 * it anyway, otherwise FALSE
2019 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2022 gboolean retval = TRUE;
2024 if (is_uri_string(uri->uri) == FALSE)
2027 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
2028 uri->start, uri->end);
2029 if (visible_str == NULL)
2032 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2034 gchar *visible_uri_path;
2036 uri_path = get_uri_path(uri->uri);
2037 visible_uri_path = get_uri_path(visible_str);
2038 if (strcmp(uri_path, visible_uri_path) != 0)
2042 if (retval == FALSE) {
2046 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2047 "the apparent URL (%s).\n"
2049 uri->uri, visible_str);
2050 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"), _("No"), NULL,
2051 NULL, ALERT_WARNING);
2053 if (aval == G_ALERTDEFAULT)
2057 g_free(visible_str);
2062 static void textview_uri_list_remove_all(GSList *uri_list)
2066 for (cur = uri_list; cur != NULL; cur = cur->next) {
2068 g_free(((RemoteURI *)cur->data)->uri);
2073 g_slist_free(uri_list);