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 = {
101 static GdkColor good_sig_color = {
108 static GdkColor nocheck_sig_color = {
115 static GdkColor bad_sig_color = {
122 #define TEXTVIEW_STATUSBAR_PUSH(textview, str) \
124 gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
125 textview->messageview->statusbar_cid, str); \
128 #define TEXTVIEW_STATUSBAR_POP(textview) \
130 gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
131 textview->messageview->statusbar_cid); \
134 static void textview_show_ertf (TextView *textview,
136 CodeConverter *conv);
137 static void textview_add_part (TextView *textview,
139 static void textview_add_parts (TextView *textview,
141 static void textview_write_body (TextView *textview,
143 const gchar *charset);
144 static void textview_show_html (TextView *textview,
146 CodeConverter *conv);
148 static void textview_write_line (TextView *textview,
150 CodeConverter *conv);
151 static void textview_write_link (TextView *textview,
154 CodeConverter *conv);
156 static GPtrArray *textview_scan_header (TextView *textview,
158 static void textview_show_header (TextView *textview,
161 static gint textview_key_pressed (GtkWidget *widget,
164 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
165 GdkEvent *event, GtkTextIter *iter,
167 static void textview_smooth_scroll_do (TextView *textview,
171 static void textview_smooth_scroll_one_line (TextView *textview,
173 static gboolean textview_smooth_scroll_page (TextView *textview,
176 static gboolean textview_uri_security_check (TextView *textview,
178 static void textview_uri_list_remove_all (GSList *uri_list);
181 static void populate_popup(GtkTextView *textview, GtkMenu *menu,
184 gtk_menu_detach(menu);
187 TextView *textview_create(void)
191 GtkWidget *scrolledwin;
193 GtkTextBuffer *buffer;
194 GtkClipboard *clipboard;
195 PangoFontDescription *font_desc = NULL;
197 debug_print("Creating text view...\n");
198 textview = g_new0(TextView, 1);
200 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
201 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
202 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
203 gtk_widget_set_size_request(scrolledwin, prefs_common.mainview_width, -1);
204 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
207 /* create GtkSText widgets for single-byte and multi-byte character */
208 text = gtk_text_view_new();
209 gtk_widget_show(text);
210 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
211 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
212 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
213 g_signal_connect(G_OBJECT(text), "populate-popup",
214 G_CALLBACK(populate_popup), NULL);
217 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
218 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
219 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
221 gtk_widget_ensure_style(text);
223 if (prefs_common.normalfont)
224 font_desc = pango_font_description_from_string
225 (prefs_common.normalfont);
227 gtk_widget_modify_font(text, font_desc);
229 pango_font_description_free(font_desc);
231 gtk_widget_ref(scrolledwin);
233 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
235 g_signal_connect(G_OBJECT(text), "key_press_event",
236 G_CALLBACK(textview_key_pressed),
239 gtk_widget_show(scrolledwin);
241 vbox = gtk_vbox_new(FALSE, 0);
242 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
244 gtk_widget_show(vbox);
246 textview->vbox = vbox;
247 textview->scrolledwin = scrolledwin;
248 textview->text = text;
249 textview->uri_list = NULL;
250 textview->body_pos = 0;
251 textview->show_all_headers = FALSE;
252 textview->last_buttonpress = GDK_NOTHING;
253 textview->show_url_msgid = 0;
258 static void textview_create_tags(GtkTextView *text, TextView *textview)
260 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
263 gtk_text_buffer_create_tag(buffer, "header",
264 "pixels-above-lines", 0,
265 "pixels-above-lines-set", TRUE,
266 "pixels-below-lines", 0,
267 "pixels-below-lines-set", TRUE,
269 "left-margin-set", TRUE,
271 gtk_text_buffer_create_tag(buffer, "header_title",
272 "font", prefs_common.boldfont,
274 gtk_text_buffer_create_tag(buffer, "quote0",
275 "foreground-gdk", "e_colors[0],
277 gtk_text_buffer_create_tag(buffer, "quote1",
278 "foreground-gdk", "e_colors[1],
280 gtk_text_buffer_create_tag(buffer, "quote2",
281 "foreground-gdk", "e_colors[2],
283 gtk_text_buffer_create_tag(buffer, "emphasis",
284 "foreground-gdk", &emphasis_color,
286 gtk_text_buffer_create_tag(buffer, "signature",
287 "foreground-gdk", &signature_color,
289 tag = gtk_text_buffer_create_tag(buffer, "link",
290 "foreground-gdk", &uri_color,
293 gtk_text_buffer_create_tag(buffer, "good-signature",
294 "foreground-gdk", &good_sig_color,
296 gtk_text_buffer_create_tag(buffer, "bad-signature",
297 "foreground-gdk", &bad_sig_color,
299 gtk_text_buffer_create_tag(buffer, "nocheck-signature",
300 "foreground-gdk", &nocheck_sig_color,
302 #endif /*USE_GPGME */
304 g_signal_connect(G_OBJECT(tag), "event",
305 G_CALLBACK(textview_uri_button_pressed), textview);
308 void textview_init(TextView *textview)
312 gtkut_widget_disable_theme_engine(textview->text);
314 textview_update_message_colors();
315 textview_set_all_headers(textview, FALSE);
316 textview_set_font(textview, NULL);
318 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
321 void textview_update_message_colors(void)
323 GdkColor black = {0, 0, 0, 0};
325 if (prefs_common.enable_color) {
326 /* grab the quote colors, converting from an int to a GdkColor */
327 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
329 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
331 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
333 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
335 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
338 quote_colors[0] = quote_colors[1] = quote_colors[2] =
339 uri_color = emphasis_color = signature_color = black;
343 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
347 const gchar *charset = NULL;
349 if ((fp = fopen(file, "rb")) == NULL) {
350 FILE_OP_ERROR(file, "fopen");
354 if (textview->messageview->forced_charset)
355 charset = textview->messageview->forced_charset;
356 else if (prefs_common.force_charset)
357 charset = prefs_common.force_charset;
359 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
361 textview_set_font(textview, charset);
362 textview_clear(textview);
366 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) perror("fseek");
367 headers = textview_scan_header(textview, fp);
369 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
370 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
373 textview_show_header(textview, headers);
374 procheader_header_array_destroy(headers);
376 gtk_text_buffer_get_end_iter(buffer, &iter);
377 textview->body_pos = gtk_text_iter_get_offset(&iter);
380 textview_add_parts(textview, mimeinfo);
384 textview_set_position(textview, 0);
387 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
390 const gchar *charset = NULL;
392 g_return_if_fail(mimeinfo != NULL);
393 g_return_if_fail(fp != NULL);
395 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
396 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
397 textview_clear(textview);
398 textview_add_parts(textview, mimeinfo);
402 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
405 headers = textview_scan_header(textview, fp);
407 if (textview->messageview->forced_charset)
408 charset = textview->messageview->forced_charset;
409 else if (prefs_common.force_charset)
410 charset = prefs_common.force_charset;
412 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
414 textview_set_font(textview, charset);
416 textview_clear(textview);
420 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
421 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
424 textview_show_header(textview, headers);
425 procheader_header_array_destroy(headers);
427 gtk_text_buffer_get_end_iter(buffer, &iter);
428 textview->body_pos = gtk_text_iter_get_offset(&iter);
429 if (!mimeinfo->main) {
430 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
434 if (mimeinfo->type == MIMETYPE_MULTIPART)
435 textview_add_parts(textview, mimeinfo);
437 textview_write_body(textview, mimeinfo, charset);
441 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
444 GtkTextBuffer *buffer;
447 const gchar *charset = NULL;
448 GPtrArray *headers = NULL;
452 g_return_if_fail(mimeinfo != NULL);
453 text = GTK_TEXT_VIEW(textview->text);
454 buffer = gtk_text_view_get_buffer(text);
455 charcount = gtk_text_buffer_get_char_count(buffer);
456 gtk_text_buffer_get_end_iter(buffer, &iter);
458 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
460 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
463 fp = fopen(mimeinfo->filename, "rb");
464 fseek(fp, mimeinfo->offset, SEEK_SET);
465 headers = textview_scan_header(textview, fp);
468 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
469 textview_show_header(textview, headers);
470 procheader_header_array_destroy(headers);
476 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
478 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
480 g_snprintf(buf, sizeof(buf), "\n[%s %s/%s (%d bytes)]\n",
482 procmime_get_type_str(mimeinfo->type),
483 mimeinfo->subtype, mimeinfo->length);
485 g_snprintf(buf, sizeof(buf), "\n[%s/%s (%d bytes)]\n",
486 procmime_get_type_str(mimeinfo->type),
487 mimeinfo->subtype, mimeinfo->length);
489 if (mimeinfo->type != MIMETYPE_TEXT) {
490 gtk_text_buffer_insert(buffer, &iter, buf, -1);
491 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
492 if (prefs_common.display_header && (charcount > 0))
493 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
494 if (textview->messageview->forced_charset)
495 charset = textview->messageview->forced_charset;
496 else if (prefs_common.force_charset)
497 charset = prefs_common.force_charset;
499 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
501 textview_write_body(textview, mimeinfo, charset);
506 static gboolean add_parts_func(GNode *node, gpointer data)
508 MimeInfo *mimeinfo = (MimeInfo *) node->data;
509 TextView *textview = (TextView *) data;
511 g_return_val_if_fail(mimeinfo != NULL, FALSE);
513 textview_add_part(textview, mimeinfo);
518 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
520 g_return_if_fail(mimeinfo != NULL);
522 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, add_parts_func, textview);
526 static void recursive_add_parts(TextView *textview, GNode *node)
531 mimeinfo = (MimeInfo *) node->data;
533 textview_add_part(textview, mimeinfo);
535 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
536 (mimeinfo->type != MIMETYPE_MESSAGE))
539 if (strcasecmp(mimeinfo->subtype, "alternative") == 0) {
540 GNode * prefered_body;
548 prefered_body = NULL;
551 for(iter = g_node_first_child(node) ; iter != NULL ;
552 iter = g_node_next_sibling(iter)) {
557 submime = (MimeInfo *) iter->data;
558 if (submime->type == MIMETYPE_TEXT)
561 if (submime->subtype != NULL) {
562 if (strcasecmp(submime->subtype, "plain") == 0)
566 if (score > prefered_score) {
567 prefered_score = score;
568 prefered_body = iter;
572 if (prefered_body != NULL) {
573 recursive_add_parts(textview, prefered_body);
577 for(iter = g_node_first_child(node) ; iter != NULL ;
578 iter = g_node_next_sibling(iter)) {
579 recursive_add_parts(textview, iter);
584 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
586 g_return_if_fail(mimeinfo != NULL);
588 recursive_add_parts(textview, mimeinfo->node);
591 #define TEXT_INSERT(str) \
592 gtk_text_buffer_insert(buffer, &iter, str, -1)
594 void textview_show_error(TextView *textview)
597 GtkTextBuffer *buffer;
600 textview_set_font(textview, NULL);
601 textview_clear(textview);
603 text = GTK_TEXT_VIEW(textview->text);
604 buffer = gtk_text_view_get_buffer(text);
605 gtk_text_buffer_get_start_iter(buffer, &iter);
607 TEXT_INSERT(_("This message can't be displayed.\n"));
611 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
614 GtkTextBuffer *buffer;
617 if (!partinfo) return;
619 textview_set_font(textview, NULL);
620 textview_clear(textview);
622 text = GTK_TEXT_VIEW(textview->text);
623 buffer = gtk_text_view_get_buffer(text);
624 gtk_text_buffer_get_start_iter(buffer, &iter);
626 TEXT_INSERT(_("The following can be performed on this part by "));
627 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
629 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
630 TEXT_INSERT(_(" To display as text select 'Display as text' "));
631 TEXT_INSERT(_("(Shortcut key: 't')\n"));
632 TEXT_INSERT(_(" To open with an external program select 'Open' "));
633 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
634 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
635 TEXT_INSERT(_("mouse button),\n"));
636 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
642 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
643 const gchar *charset)
649 conv = conv_code_converter_new(charset);
651 textview->is_in_signature = FALSE;
653 if(mimeinfo->encoding_type != ENC_BINARY &&
654 mimeinfo->encoding_type != ENC_7BIT &&
655 mimeinfo->encoding_type != ENC_8BIT)
656 procmime_decode_content(mimeinfo);
658 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
661 filename = procmime_get_tmp_file_name(mimeinfo);
662 if (procmime_get_part(filename, mimeinfo) == 0) {
663 tmpfp = fopen(filename, "rb");
664 textview_show_html(textview, tmpfp, conv);
669 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
672 filename = procmime_get_tmp_file_name(mimeinfo);
673 if (procmime_get_part(filename, mimeinfo) == 0) {
674 tmpfp = fopen(filename, "rb");
675 textview_show_ertf(textview, tmpfp, conv);
681 tmpfp = fopen(mimeinfo->filename, "rb");
682 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
683 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
684 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
685 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
686 textview_write_line(textview, buf, conv);
690 conv_code_converter_destroy(conv);
693 static void textview_show_html(TextView *textview, FILE *fp,
699 parser = html_parser_new(fp, conv);
700 g_return_if_fail(parser != NULL);
702 while ((str = html_parse(parser)) != NULL) {
703 if (parser->state == HTML_HREF) {
704 /* first time : get and copy the URL */
705 if (parser->href == NULL) {
706 /* ALF - the sylpheed html parser returns an empty string,
707 * if still inside an <a>, but already parsed past HREF */
708 str = strtok(str, " ");
710 parser->href = strdup(str);
711 /* the URL may (or not) be followed by the
713 str = strtok(NULL, "");
717 textview_write_link(textview, str, parser->href, NULL);
719 textview_write_line(textview, str, NULL);
721 html_parser_destroy(parser);
724 static void textview_show_ertf(TextView *textview, FILE *fp,
730 parser = ertf_parser_new(fp, conv);
731 g_return_if_fail(parser != NULL);
733 while ((str = ertf_parse(parser)) != NULL) {
734 textview_write_line(textview, str, NULL);
737 ertf_parser_destroy(parser);
740 /* get_uri_part() - retrieves a URI starting from scanpos.
741 Returns TRUE if succesful */
742 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
743 const gchar **bp, const gchar **ep)
747 g_return_val_if_fail(start != NULL, FALSE);
748 g_return_val_if_fail(scanpos != NULL, FALSE);
749 g_return_val_if_fail(bp != NULL, FALSE);
750 g_return_val_if_fail(ep != NULL, FALSE);
754 /* find end point of URI */
755 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
756 if (!isgraph(*(const guchar *)ep_) ||
757 !isascii(*(const guchar *)ep_) ||
758 strchr("()<>\"", *ep_))
762 /* no punctuation at end of string */
764 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
765 * should pass some URI type to this function and decide on that whether
766 * to perform punctuation stripping */
768 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
770 for (; ep_ - 1 > scanpos + 1 &&
771 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
782 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
784 return g_strndup(bp, ep - bp);
787 /* valid mail address characters */
788 #define IS_RFC822_CHAR(ch) \
793 !strchr("(),;<>\"", (ch)))
795 /* alphabet and number within 7bit ASCII */
796 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
797 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
799 static GHashTable *create_domain_tab(void)
801 static const gchar *toplvl_domains [] = {
803 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
804 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
805 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
806 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
807 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
808 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
809 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
810 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
811 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
812 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
813 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
814 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
815 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
816 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
817 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
818 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
819 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
820 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
821 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
822 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
823 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
824 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
825 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
826 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
827 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
828 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
831 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
833 g_return_val_if_fail(htab, NULL);
834 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
835 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
839 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
841 const gint MAX_LVL_DOM_NAME_LEN = 6;
842 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
843 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
846 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
849 for (p = buf; p < m && first < last; *p++ = *first++)
853 return g_hash_table_lookup(tab, buf) != NULL;
856 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
857 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
858 const gchar **bp, const gchar **ep)
860 /* more complex than the uri part because we need to scan back and forward starting from
861 * the scan position. */
862 gboolean result = FALSE;
863 const gchar *bp_ = NULL;
864 const gchar *ep_ = NULL;
865 static GHashTable *dom_tab;
866 const gchar *last_dot = NULL;
867 const gchar *prelast_dot = NULL;
868 const gchar *last_tld_char = NULL;
870 /* the informative part of the email address (describing the name
871 * of the email address owner) may contain quoted parts. the
872 * closure stack stores the last encountered quotes. */
873 gchar closure_stack[128];
874 gchar *ptr = closure_stack;
876 g_return_val_if_fail(start != NULL, FALSE);
877 g_return_val_if_fail(scanpos != NULL, FALSE);
878 g_return_val_if_fail(bp != NULL, FALSE);
879 g_return_val_if_fail(ep != NULL, FALSE);
882 dom_tab = create_domain_tab();
883 g_return_val_if_fail(dom_tab, FALSE);
885 /* scan start of address */
886 for (bp_ = scanpos - 1;
887 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
890 /* TODO: should start with an alnum? */
892 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
895 if (bp_ != scanpos) {
896 /* scan end of address */
897 for (ep_ = scanpos + 1;
898 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
900 prelast_dot = last_dot;
902 if (*(last_dot + 1) == '.') {
903 if (prelast_dot == NULL)
905 last_dot = prelast_dot;
910 /* TODO: really should terminate with an alnum? */
911 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
916 if (last_dot == NULL)
919 last_dot = prelast_dot;
920 if (last_dot == NULL || (scanpos + 1 >= last_dot))
924 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
925 if (*last_tld_char == '?')
928 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
935 if (!result) return FALSE;
937 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
938 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
941 /* see if this is <bracketed>; in this case we also scan for the informative part. */
942 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
945 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
946 #define IN_STACK() (ptr > closure_stack)
947 /* has underrun check */
948 #define POP_STACK() if(IN_STACK()) --ptr
949 /* has overrun check */
950 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
951 /* has underrun check */
952 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
956 /* scan for the informative part. */
957 for (bp_ -= 2; bp_ >= start; bp_--) {
958 /* if closure on the stack keep scanning */
959 if (PEEK_STACK() == *bp_) {
963 if (*bp_ == '\'' || *bp_ == '"') {
968 /* if nothing in the closure stack, do the special conditions
969 * the following if..else expression simply checks whether
970 * a token is acceptable. if not acceptable, the clause
971 * should terminate the loop with a 'break' */
974 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
975 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
976 /* hyphens are allowed, but only in
978 } else if (!ispunct(*bp_)) {
979 /* but anything not being a punctiation
982 break; /* anything else is rejected */
995 /* scan forward (should start with an alnum) */
996 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
1006 #undef IS_ASCII_ALNUM
1007 #undef IS_RFC822_CHAR
1009 static gchar *make_email_string(const gchar *bp, const gchar *ep)
1011 /* returns a mailto: URI; mailto: is also used to detect the
1012 * uri type later on in the button_pressed signal handler */
1016 tmp = g_strndup(bp, ep - bp);
1017 result = g_strconcat("mailto:", tmp, NULL);
1023 static gchar *make_http_string(const gchar *bp, const gchar *ep)
1025 /* returns an http: URI; */
1029 tmp = g_strndup(bp, ep - bp);
1030 result = g_strconcat("http://", tmp, NULL);
1036 #define ADD_TXT_POS(bp_, ep_, pti_) \
1037 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1038 last = last->next; \
1039 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1040 last->next = NULL; \
1042 g_warning("alloc error scanning URIs\n"); \
1043 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1049 /* textview_make_clickable_parts() - colorizes clickable parts */
1050 static void textview_make_clickable_parts(TextView *textview,
1051 const gchar *fg_tag,
1052 const gchar *uri_tag,
1053 const gchar *linebuf)
1055 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1056 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1059 /* parse table - in order of priority */
1061 const gchar *needle; /* token */
1063 /* token search function */
1064 gchar *(*search) (const gchar *haystack,
1065 const gchar *needle);
1066 /* part parsing function */
1067 gboolean (*parse) (const gchar *start,
1068 const gchar *scanpos,
1071 /* part to URI function */
1072 gchar *(*build_uri) (const gchar *bp,
1076 static struct table parser[] = {
1077 {"http://", strcasestr, get_uri_part, make_uri_string},
1078 {"https://", strcasestr, get_uri_part, make_uri_string},
1079 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1080 {"www.", strcasestr, get_uri_part, make_http_string},
1081 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1082 {"@", strcasestr, get_email_part, make_email_string}
1084 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1087 const gchar *walk, *bp, *ep;
1090 const gchar *bp, *ep; /* text position */
1091 gint pti; /* index in parse table */
1092 struct txtpos *next; /* next */
1093 } head = {NULL, NULL, 0, NULL}, *last = &head;
1095 gtk_text_buffer_get_end_iter(buffer, &iter);
1097 /* parse for clickable parts, and build a list of begin and end positions */
1098 for (walk = linebuf, n = 0;;) {
1099 gint last_index = PARSE_ELEMS;
1100 gchar *scanpos = NULL;
1102 /* FIXME: this looks phony. scanning for anything in the parse table */
1103 for (n = 0; n < PARSE_ELEMS; n++) {
1106 tmp = parser[n].search(walk, parser[n].needle);
1108 if (scanpos == NULL || tmp < scanpos) {
1116 /* check if URI can be parsed */
1117 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1118 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1119 ADD_TXT_POS(bp, ep, last_index);
1123 strlen(parser[last_index].needle);
1128 /* colorize this line */
1130 const gchar *normal_text = linebuf;
1133 for (last = head.next; last != NULL;
1134 normal_text = last->ep, last = last->next) {
1137 uri = g_new(RemoteURI, 1);
1138 if (last->bp - normal_text > 0)
1139 gtk_text_buffer_insert_with_tags_by_name
1142 last->bp - normal_text,
1144 uri->uri = parser[last->pti].build_uri(last->bp,
1146 uri->start = gtk_text_iter_get_offset(&iter);
1147 gtk_text_buffer_insert_with_tags_by_name
1149 last->bp, last->ep - last->bp,
1151 uri->end = gtk_text_iter_get_offset(&iter);
1152 textview->uri_list =
1153 g_slist_append(textview->uri_list, uri);
1157 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1161 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1169 static void textview_write_line(TextView *textview, const gchar *str,
1170 CodeConverter *conv)
1173 GtkTextBuffer *buffer;
1175 gchar buf[BUFFSIZE];
1177 gint quotelevel = -1;
1178 gchar quote_tag_str[10];
1180 text = GTK_TEXT_VIEW(textview->text);
1181 buffer = gtk_text_view_get_buffer(text);
1182 gtk_text_buffer_get_end_iter(buffer, &iter);
1187 if (textview->text_is_mb)
1188 conv_localetodisp(buf, sizeof(buf), str);
1190 strncpy2(buf, str, sizeof(buf));
1191 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1192 conv_localetodisp(buf, sizeof(buf), str);
1193 else if (textview->text_is_mb)
1194 conv_unreadable_locale(buf);
1197 strncpy2(buf, str, sizeof(buf));
1198 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1199 conv_localetodisp(buf, sizeof(buf), str);
1203 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1206 /* change color of quotation
1207 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1208 Up to 3 levels of quotations are detected, and each
1209 level is colored using a different color. */
1210 if (prefs_common.enable_color
1211 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1212 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1214 /* set up the correct foreground color */
1215 if (quotelevel > 2) {
1216 /* recycle colors */
1217 if (prefs_common.recycle_quote_colors)
1224 if (quotelevel == -1)
1227 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1228 "quote%d", quotelevel);
1229 fg_color = quote_tag_str;
1232 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1233 fg_color = "signature";
1234 textview->is_in_signature = TRUE;
1237 if (prefs_common.enable_color)
1238 textview_make_clickable_parts(textview, fg_color, "link", buf);
1240 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1243 void textview_write_link(TextView *textview, const gchar *str,
1244 const gchar *uri, CodeConverter *conv)
1246 GdkColor *link_color = NULL;
1248 GtkTextBuffer *buffer;
1250 gchar buf[BUFFSIZE];
1257 text = GTK_TEXT_VIEW(textview->text);
1258 buffer = gtk_text_view_get_buffer(text);
1259 gtk_text_buffer_get_end_iter(buffer, &iter);
1264 if (textview->text_is_mb)
1265 conv_localetodisp(buf, sizeof(buf), str);
1267 strncpy2(buf, str, sizeof(buf));
1268 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1269 conv_localetodisp(buf, sizeof(buf), str);
1270 else if (textview->text_is_mb)
1271 conv_unreadable_locale(buf);
1274 strncpy2(buf, str, sizeof(buf));
1275 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1276 conv_localetodisp(buf, sizeof(buf), str);
1281 gtk_text_buffer_get_end_iter(buffer, &iter);
1283 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1284 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1286 if (prefs_common.enable_color) {
1287 link_color = &uri_color;
1289 r_uri = g_new(RemoteURI, 1);
1290 r_uri->uri = g_strdup(uri);
1291 r_uri->start = gtk_text_iter_get_offset(&iter);
1292 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1294 r_uri->end = gtk_text_iter_get_offset(&iter);
1295 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1298 void textview_clear(TextView *textview)
1300 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1301 GtkTextBuffer *buffer;
1303 buffer = gtk_text_view_get_buffer(text);
1304 gtk_text_buffer_set_text(buffer, "\0", -1);
1306 TEXTVIEW_STATUSBAR_POP(textview);
1307 textview_uri_list_remove_all(textview->uri_list);
1308 textview->uri_list = NULL;
1310 textview->body_pos = 0;
1313 void textview_destroy(TextView *textview)
1315 textview_uri_list_remove_all(textview->uri_list);
1316 textview->uri_list = NULL;
1321 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1323 textview->show_all_headers = all_headers;
1326 void textview_set_font(TextView *textview, const gchar *codeset)
1328 if (prefs_common.textfont) {
1329 PangoFontDescription *font_desc = NULL;
1331 if (prefs_common.textfont)
1332 font_desc = pango_font_description_from_string
1333 (prefs_common.textfont);
1335 gtk_widget_modify_font(textview->text, font_desc);
1336 pango_font_description_free(font_desc);
1339 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1340 prefs_common.line_space / 2);
1341 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1342 prefs_common.line_space / 2);
1343 if (prefs_common.head_space) {
1344 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1346 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1350 void textview_set_text(TextView *textview, const gchar *text)
1353 GtkTextBuffer *buffer;
1355 g_return_if_fail(textview != NULL);
1356 g_return_if_fail(text != NULL);
1358 textview_clear(textview);
1360 view = GTK_TEXT_VIEW(textview->text);
1361 buffer = gtk_text_view_get_buffer(view);
1362 gtk_text_buffer_set_text(buffer, text, strlen(text));
1378 H_ORGANIZATION = 11,
1381 void textview_set_position(TextView *textview, gint pos)
1383 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1384 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1387 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1388 gtk_text_buffer_place_cursor(buffer, &iter);
1391 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1393 gchar buf[BUFFSIZE];
1394 GPtrArray *headers, *sorted_headers;
1395 GSList *disphdr_list;
1399 g_return_val_if_fail(fp != NULL, NULL);
1401 if (textview->show_all_headers)
1402 return procheader_get_header_array_asis(fp);
1404 if (!prefs_common.display_header) {
1405 while (fgets(buf, sizeof(buf), fp) != NULL)
1406 if (buf[0] == '\r' || buf[0] == '\n') break;
1410 headers = procheader_get_header_array_asis(fp);
1412 sorted_headers = g_ptr_array_new();
1414 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1415 disphdr_list = disphdr_list->next) {
1416 DisplayHeaderProp *dp =
1417 (DisplayHeaderProp *)disphdr_list->data;
1419 for (i = 0; i < headers->len; i++) {
1420 header = g_ptr_array_index(headers, i);
1422 if (procheader_headername_equal(header->name,
1425 procheader_header_free(header);
1427 g_ptr_array_add(sorted_headers, header);
1429 g_ptr_array_remove_index(headers, i);
1435 if (prefs_common.show_other_header) {
1436 for (i = 0; i < headers->len; i++) {
1437 header = g_ptr_array_index(headers, i);
1438 g_ptr_array_add(sorted_headers, header);
1440 g_ptr_array_free(headers, TRUE);
1442 procheader_header_array_destroy(headers);
1445 return sorted_headers;
1448 static void textview_show_header(TextView *textview, GPtrArray *headers)
1450 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1451 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1456 g_return_if_fail(headers != NULL);
1458 for (i = 0; i < headers->len; i++) {
1459 header = g_ptr_array_index(headers, i);
1460 g_return_if_fail(header->name != NULL);
1462 gtk_text_buffer_get_end_iter (buffer, &iter);
1463 gtk_text_buffer_insert_with_tags_by_name
1464 (buffer, &iter, header->name, -1,
1465 "header_title", "header", NULL);
1466 if (header->name[strlen(header->name) - 1] != ' ')
1469 gtk_stext_insert(text, textview->boldfont,
1470 NULL, NULL, " ", 1);
1472 gtk_text_buffer_insert_with_tags_by_name
1473 (buffer, &iter, " ", 1,
1474 "header_title", "header", NULL);
1477 if (procheader_headername_equal(header->name, "Subject") ||
1478 procheader_headername_equal(header->name, "From") ||
1479 procheader_headername_equal(header->name, "To") ||
1480 procheader_headername_equal(header->name, "Cc"))
1481 unfold_line(header->body);
1485 if (textview->text_is_mb == TRUE)
1486 conv_unreadable_locale(header->body);
1489 if (prefs_common.enable_color &&
1490 (procheader_headername_equal(header->name, "X-Mailer") ||
1491 procheader_headername_equal(header->name,
1493 strstr(header->body, "Sylpheed") != NULL) {
1494 gtk_text_buffer_get_end_iter (buffer, &iter);
1495 gtk_text_buffer_insert_with_tags_by_name
1496 (buffer, &iter, header->body, -1,
1497 "header", "emphasis", NULL);
1498 } else if (prefs_common.enable_color) {
1499 textview_make_clickable_parts(textview, "header", "link",
1502 textview_make_clickable_parts(textview, "header", NULL,
1505 gtk_text_buffer_get_end_iter (buffer, &iter);
1506 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1511 gboolean textview_search_string(TextView *textview, const gchar *str,
1514 #warning FIXME_GTK2 /* currently, these search functions ignores case_sens */
1516 GtkSText *text = GTK_STEXT(textview->text);
1520 g_return_val_if_fail(str != NULL, FALSE);
1522 len = get_mbs_len(str);
1523 g_return_val_if_fail(len >= 0, FALSE);
1525 pos = textview->cur_pos;
1526 if (pos < textview->body_pos)
1527 pos = textview->body_pos;
1529 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1530 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1531 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1532 textview_set_position(textview, pos + len);
1538 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1539 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1541 GtkTextIter iter, start, end, *pos;
1543 gint insert_offset, selbound_offset;
1545 /* reset selection */
1546 mark = gtk_text_buffer_get_mark(buffer, "insert");
1547 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1548 insert_offset = gtk_text_iter_get_offset(&start);
1549 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1550 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1551 selbound_offset = gtk_text_iter_get_offset(&end);
1553 pos = insert_offset > selbound_offset ? &start : &end;
1554 gtk_text_buffer_place_cursor(buffer, pos);
1557 mark = gtk_text_buffer_get_insert(buffer);
1558 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1559 found = gtk_text_iter_forward_search(&iter, str,
1560 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1561 &start, &end, NULL);
1563 gtk_text_buffer_place_cursor(buffer, &start);
1564 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", &end);
1565 mark = gtk_text_buffer_get_mark(buffer, "insert");
1566 gtk_text_view_scroll_mark_onscreen(text, mark);
1573 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1578 GtkSText *text = GTK_STEXT(textview->text);
1583 gboolean found = FALSE;
1585 g_return_val_if_fail(str != NULL, FALSE);
1587 wcs = strdup_mbstowcs(str);
1588 g_return_val_if_fail(wcs != NULL, FALSE);
1590 pos = textview->cur_pos;
1591 text_len = gtk_stext_get_length(text);
1592 if (text_len - textview->body_pos < len) {
1596 if (pos <= textview->body_pos || text_len - pos < len)
1597 pos = text_len - len;
1599 for (; pos >= textview->body_pos; pos--) {
1600 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1602 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1603 gtk_editable_select_region(GTK_EDITABLE(text),
1605 textview_set_position(textview, pos - 1);
1609 if (pos == textview->body_pos) break;
1615 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1616 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1618 GtkTextIter iter, start, end, *pos;
1620 gint insert_offset, selbound_offset;
1622 /* reset selection */
1623 mark = gtk_text_buffer_get_mark(buffer, "insert");
1624 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1625 insert_offset = gtk_text_iter_get_offset(&start);
1626 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1627 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1628 selbound_offset = gtk_text_iter_get_offset(&end);
1630 pos = insert_offset < selbound_offset ? &start : &end;
1631 gtk_text_buffer_place_cursor(buffer, pos);
1634 mark = gtk_text_buffer_get_insert(buffer);
1635 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1636 found = gtk_text_iter_backward_search(&iter, str,
1637 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1638 &start, &end, NULL);
1640 gtk_text_buffer_place_cursor(buffer, &end);
1641 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", &start);
1642 mark = gtk_text_buffer_get_mark(buffer, "insert");
1643 gtk_text_view_scroll_mark_onscreen(text, mark);
1650 void textview_scroll_one_line(TextView *textview, gboolean up)
1652 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1653 GtkAdjustment *vadj = text->vadjustment;
1656 if (prefs_common.enable_smooth_scroll) {
1657 textview_smooth_scroll_one_line(textview, up);
1662 upper = vadj->upper - vadj->page_size;
1663 if (vadj->value < upper) {
1665 vadj->step_increment * 4;
1667 MIN(vadj->value, upper);
1668 g_signal_emit_by_name(G_OBJECT(vadj),
1669 "value_changed", 0);
1672 if (vadj->value > 0.0) {
1674 vadj->step_increment * 4;
1676 MAX(vadj->value, 0.0);
1677 g_signal_emit_by_name(G_OBJECT(vadj),
1678 "value_changed", 0);
1683 gboolean textview_scroll_page(TextView *textview, gboolean up)
1685 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1686 GtkAdjustment *vadj = text->vadjustment;
1690 if (prefs_common.enable_smooth_scroll)
1691 return textview_smooth_scroll_page(textview, up);
1693 if (prefs_common.scroll_halfpage)
1694 page_incr = vadj->page_increment / 2;
1696 page_incr = vadj->page_increment;
1699 upper = vadj->upper - vadj->page_size;
1700 if (vadj->value < upper) {
1701 vadj->value += page_incr;
1702 vadj->value = MIN(vadj->value, upper);
1703 g_signal_emit_by_name(G_OBJECT(vadj),
1704 "value_changed", 0);
1708 if (vadj->value > 0.0) {
1709 vadj->value -= page_incr;
1710 vadj->value = MAX(vadj->value, 0.0);
1711 g_signal_emit_by_name(G_OBJECT(vadj),
1712 "value_changed", 0);
1720 static void textview_smooth_scroll_do(TextView *textview,
1721 gfloat old_value, gfloat last_value,
1724 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1725 GtkAdjustment *vadj = text->vadjustment;
1730 if (old_value < last_value) {
1731 change_value = last_value - old_value;
1734 change_value = old_value - last_value;
1739 /* gdk_key_repeat_disable(); */
1741 for (i = step; i <= change_value; i += step) {
1742 vadj->value = old_value + (up ? -i : i);
1743 g_signal_emit_by_name(G_OBJECT(vadj),
1744 "value_changed", 0);
1747 vadj->value = last_value;
1748 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1751 /* gdk_key_repeat_restore(); */
1754 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1756 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1757 GtkAdjustment *vadj = text->vadjustment;
1763 upper = vadj->upper - vadj->page_size;
1764 if (vadj->value < upper) {
1765 old_value = vadj->value;
1766 last_value = vadj->value +
1767 vadj->step_increment * 4;
1768 last_value = MIN(last_value, upper);
1770 textview_smooth_scroll_do(textview, old_value,
1772 prefs_common.scroll_step);
1775 if (vadj->value > 0.0) {
1776 old_value = vadj->value;
1777 last_value = vadj->value -
1778 vadj->step_increment * 4;
1779 last_value = MAX(last_value, 0.0);
1781 textview_smooth_scroll_do(textview, old_value,
1783 prefs_common.scroll_step);
1788 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1790 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1791 GtkAdjustment *vadj = text->vadjustment;
1797 if (prefs_common.scroll_halfpage)
1798 page_incr = vadj->page_increment / 2;
1800 page_incr = vadj->page_increment;
1803 upper = vadj->upper - vadj->page_size;
1804 if (vadj->value < upper) {
1805 old_value = vadj->value;
1806 last_value = vadj->value + page_incr;
1807 last_value = MIN(last_value, upper);
1809 textview_smooth_scroll_do(textview, old_value,
1811 prefs_common.scroll_step);
1815 if (vadj->value > 0.0) {
1816 old_value = vadj->value;
1817 last_value = vadj->value - page_incr;
1818 last_value = MAX(last_value, 0.0);
1820 textview_smooth_scroll_do(textview, old_value,
1822 prefs_common.scroll_step);
1832 #define KEY_PRESS_EVENT_STOP() \
1833 if (gtk_signal_n_emissions_by_name \
1834 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1835 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1836 "key_press_event"); \
1839 #define KEY_PRESS_EVENT_STOP() \
1840 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1844 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1847 SummaryView *summaryview = NULL;
1848 MessageView *messageview = textview->messageview;
1850 if (!event) return FALSE;
1851 if (messageview->mainwin)
1852 summaryview = messageview->mainwin->summaryview;
1854 switch (event->keyval) {
1869 summary_pass_key_press_event(summaryview, event);
1871 textview_scroll_page(textview, FALSE);
1874 textview_scroll_page(textview, TRUE);
1877 textview_scroll_one_line(textview,
1878 (event->state & GDK_MOD1_MASK) != 0);
1882 summary_pass_key_press_event(summaryview, event);
1887 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1888 KEY_PRESS_EVENT_STOP();
1889 mimeview_pass_key_press_event(messageview->mimeview,
1893 /* possible fall through */
1896 event->window != messageview->mainwin->window->window) {
1897 GdkEventKey tmpev = *event;
1899 tmpev.window = messageview->mainwin->window->window;
1900 KEY_PRESS_EVENT_STOP();
1901 gtk_widget_event(messageview->mainwin->window,
1902 (GdkEvent *)&tmpev);
1910 static gint show_url_timeout_cb(gpointer data)
1912 TextView *textview = (TextView *)data;
1914 TEXTVIEW_STATUSBAR_POP(textview);
1919 *\brief Check to see if a web URL has been disguised as a different
1920 * URL (possible with HTML email).
1922 *\param uri The uri to check
1924 *\param textview The TextView the URL is contained in
1926 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1927 * it anyway, otherwise FALSE
1929 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1932 gboolean retval = TRUE;
1934 if (g_strncasecmp(uri->uri, "http:", 5) &&
1935 g_strncasecmp(uri->uri, "https:", 6) &&
1936 g_strncasecmp(uri->uri, "www.", 4))
1939 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1942 if (clicked_str == NULL)
1945 if (strcmp(clicked_str, uri->uri) &&
1946 (!g_strncasecmp(clicked_str, "http:", 5) ||
1947 !g_strncasecmp(clicked_str, "https:", 6) ||
1948 !g_strncasecmp(clicked_str, "www.", 4))) {
1952 /* allow uri->uri == http://somewhere.com
1953 and clicked_str == somewhere.com */
1954 str = g_strconcat("http://", clicked_str, NULL);
1956 if (!g_strcasecmp(str, uri->uri))
1961 if (retval == FALSE) {
1965 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1966 "the apparent URL (%s). \n"
1968 uri->uri, clicked_str);
1969 resp = alertpanel(_("Warning"),
1975 if (resp == G_ALERTDEFAULT)
1978 g_free(clicked_str);
1982 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1983 GdkEvent *event, GtkTextIter *iter,
1986 GtkTextIter start_iter, end_iter;
1987 gint start_pos, end_pos;
1988 GdkEventButton *bevent;
1992 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
1993 && event->type != GDK_MOTION_NOTIFY)
1996 bevent = (GdkEventButton *) event;
1998 /* get start and end positions */
2000 if(!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) {
2001 debug_print("Can't find start.");
2004 start_pos = gtk_text_iter_get_offset(&start_iter);
2007 if(!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) {
2008 debug_print("Can't find end");
2011 end_pos = gtk_text_iter_get_offset(&end_iter);
2013 /* search current uri */
2014 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2015 RemoteURI *uri = (RemoteURI *)cur->data;
2017 if (start_pos != uri->start || end_pos != uri->end)
2020 trimmed_uri = trim_string(uri->uri, 60);
2021 /* hover or single click: display url in statusbar */
2022 if (event->type == GDK_MOTION_NOTIFY
2023 || (event->type == GDK_BUTTON_PRESS && bevent->button == 1)) {
2024 if (textview->messageview->mainwin) {
2025 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
2026 textview->show_url_timeout_tag = gtk_timeout_add
2027 (4000, show_url_timeout_cb, textview);
2031 /* doubleclick: open compose / add address / browser */
2032 if ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) ||
2033 bevent->button == 2 || bevent->button == 3) {
2034 if (!g_strncasecmp(uri->uri, "mailto:", 7))
2035 if (bevent->button == 3) {
2036 gchar *fromname, *fromaddress;
2039 fromaddress = g_strdup(uri->uri + 7);
2040 /* Hiroyuki: please put this function in utils.c! */
2041 fromname = procheader_get_fromname(fromaddress);
2042 extract_address(fromaddress);
2043 g_message("adding from textview %s <%s>", fromname, fromaddress);
2044 /* Add to address book - Match */
2045 addressbook_add_contact( fromname, fromaddress, NULL );
2047 g_free(fromaddress);
2050 PrefsAccount *account = NULL;
2051 FolderItem *folder_item;
2053 if (textview->messageview && textview->messageview->mainwin
2054 && textview->messageview->mainwin->summaryview
2055 && textview->messageview->mainwin->summaryview->folder_item) {
2056 folder_item = textview->messageview->mainwin->summaryview->folder_item;
2057 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2058 account = account_find_from_id(folder_item->prefs->default_account);
2060 compose_new(account, uri->uri + 7, NULL);
2063 if (uri_security_check(uri, textview) == TRUE)
2064 open_uri(uri->uri, prefs_common.uri_cmd);
2067 g_free(trimmed_uri);
2074 *\brief Check to see if a web URL has been disguised as a different
2075 * URL (possible with HTML email).
2077 *\param uri The uri to check
2079 *\param textview The TextView the URL is contained in
2081 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2082 * it anyway, otherwise FALSE
2084 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2087 gboolean retval = TRUE;
2089 if (is_uri_string(uri->uri) == FALSE)
2092 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
2093 uri->start, uri->end);
2094 if (visible_str == NULL)
2097 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2099 gchar *visible_uri_path;
2101 uri_path = get_uri_path(uri->uri);
2102 visible_uri_path = get_uri_path(visible_str);
2103 if (strcmp(uri_path, visible_uri_path) != 0)
2107 if (retval == FALSE) {
2111 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2112 "the apparent URL (%s).\n"
2114 uri->uri, visible_str);
2115 aval = alertpanel(_("Warning"), msg, _("Yes"), _("No"), NULL);
2117 if (aval == G_ALERTDEFAULT)
2121 g_free(visible_str);
2126 static void textview_uri_list_remove_all(GSList *uri_list)
2130 for (cur = uri_list; cur != NULL; cur = cur->next) {
2132 g_free(((RemoteURI *)cur->data)->uri);
2137 g_slist_free(uri_list);