2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2003 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 static void textview_show_ertf (TextView *textview,
124 CodeConverter *conv);
125 static void textview_add_part (TextView *textview,
127 static void textview_add_parts (TextView *textview,
129 static void textview_write_body (TextView *textview,
131 const gchar *charset);
132 static void textview_show_html (TextView *textview,
134 CodeConverter *conv);
135 static void textview_write_line (TextView *textview,
137 CodeConverter *conv);
138 static void textview_write_link (TextView *textview,
141 CodeConverter *conv);
142 static GPtrArray *textview_scan_header (TextView *textview,
144 static void textview_show_header (TextView *textview,
147 static gint textview_key_pressed (GtkWidget *widget,
150 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
151 GdkEvent *event, GtkTextIter *iter,
153 static void textview_uri_list_remove_all(GSList *uri_list);
155 static void textview_smooth_scroll_do (TextView *textview,
159 static void textview_smooth_scroll_one_line (TextView *textview,
161 static gboolean textview_smooth_scroll_page (TextView *textview,
164 static void populate_popup(GtkTextView *textview, GtkMenu *menu,
167 gtk_menu_detach(menu);
170 TextView *textview_create(void)
174 GtkWidget *scrolledwin;
176 GtkTextBuffer *buffer;
177 GtkClipboard *clipboard;
178 PangoFontDescription *font_desc = NULL;
180 debug_print("Creating text view...\n");
181 textview = g_new0(TextView, 1);
183 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
184 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
185 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
186 gtk_widget_set_size_request(scrolledwin, prefs_common.mainview_width, -1);
188 /* create GtkSText widgets for single-byte and multi-byte character */
189 text = gtk_text_view_new();
190 gtk_widget_show(text);
191 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
192 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
193 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
194 g_signal_connect(G_OBJECT(text), "populate-popup",
195 G_CALLBACK(populate_popup), NULL);
198 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
199 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
200 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
202 gtk_widget_ensure_style(text);
204 if (prefs_common.normalfont)
205 font_desc = pango_font_description_from_string
206 (prefs_common.normalfont);
208 gtk_widget_modify_font(text, font_desc);
210 pango_font_description_free(font_desc);
212 gtk_widget_ref(scrolledwin);
214 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
216 g_signal_connect(G_OBJECT(text), "key_press_event",
217 G_CALLBACK(textview_key_pressed),
220 gtk_widget_show(scrolledwin);
222 vbox = gtk_vbox_new(FALSE, 0);
223 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
225 gtk_widget_show(vbox);
227 textview->vbox = vbox;
228 textview->scrolledwin = scrolledwin;
229 textview->text = text;
230 textview->uri_list = NULL;
231 textview->body_pos = 0;
232 textview->show_all_headers = FALSE;
233 textview->last_buttonpress = GDK_NOTHING;
234 textview->show_url_msgid = 0;
239 static void textview_create_tags(GtkTextView *text, TextView *textview)
241 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
244 gtk_text_buffer_create_tag(buffer, "header",
245 "pixels-above-lines", 0,
246 "pixels-above-lines-set", TRUE,
247 "pixels-below-lines", 0,
248 "pixels-below-lines-set", TRUE,
250 "left-margin-set", TRUE,
252 gtk_text_buffer_create_tag(buffer, "header_title",
253 "font", prefs_common.boldfont,
255 gtk_text_buffer_create_tag(buffer, "quote0",
256 "foreground-gdk", "e_colors[0],
258 gtk_text_buffer_create_tag(buffer, "quote1",
259 "foreground-gdk", "e_colors[1],
261 gtk_text_buffer_create_tag(buffer, "quote2",
262 "foreground-gdk", "e_colors[2],
264 gtk_text_buffer_create_tag(buffer, "emphasis",
265 "foreground-gdk", &emphasis_color,
267 gtk_text_buffer_create_tag(buffer, "signature",
268 "foreground-gdk", &signature_color,
270 tag = gtk_text_buffer_create_tag(buffer, "link",
271 "foreground-gdk", &uri_color,
274 gtk_text_buffer_create_tag(buffer, "good-signature",
275 "foreground-gdk", &good_sig_color,
277 gtk_text_buffer_create_tag(buffer, "bad-signature",
278 "foreground-gdk", &bad_sig_color,
280 gtk_text_buffer_create_tag(buffer, "nocheck-signature",
281 "foreground-gdk", &nocheck_sig_color,
283 #endif /*USE_GPGME */
285 g_signal_connect(G_OBJECT(tag), "event",
286 G_CALLBACK(textview_uri_button_pressed), textview);
289 void textview_init(TextView *textview)
293 gtkut_widget_disable_theme_engine(textview->text);
295 textview_update_message_colors();
296 textview_set_all_headers(textview, FALSE);
297 textview_set_font(textview, NULL);
299 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
302 void textview_update_message_colors(void)
304 GdkColor black = {0, 0, 0, 0};
306 if (prefs_common.enable_color) {
307 /* grab the quote colors, converting from an int to a GdkColor */
308 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
310 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
312 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
314 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
316 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
319 quote_colors[0] = quote_colors[1] = quote_colors[2] =
320 uri_color = emphasis_color = signature_color = black;
324 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
328 const gchar *charset = NULL;
330 if ((fp = fopen(file, "rb")) == NULL) {
331 FILE_OP_ERROR(file, "fopen");
335 if (textview->messageview->forced_charset)
336 charset = textview->messageview->forced_charset;
337 else if (prefs_common.force_charset)
338 charset = prefs_common.force_charset;
340 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
342 textview_set_font(textview, charset);
343 textview_clear(textview);
347 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) perror("fseek");
348 headers = textview_scan_header(textview, fp);
350 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
351 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
354 textview_show_header(textview, headers);
355 procheader_header_array_destroy(headers);
357 gtk_text_buffer_get_end_iter(buffer, &iter);
358 textview->body_pos = gtk_text_iter_get_offset(&iter);
361 textview_add_parts(textview, mimeinfo);
365 textview_set_position(textview, 0);
368 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
371 const gchar *charset = NULL;
373 g_return_if_fail(mimeinfo != NULL);
374 g_return_if_fail(fp != NULL);
376 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
377 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
378 textview_clear(textview);
379 textview_add_parts(textview, mimeinfo);
383 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
386 headers = textview_scan_header(textview, fp);
388 if (textview->messageview->forced_charset)
389 charset = textview->messageview->forced_charset;
390 else if (prefs_common.force_charset)
391 charset = prefs_common.force_charset;
393 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
395 textview_set_font(textview, charset);
397 textview_clear(textview);
401 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
402 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
405 textview_show_header(textview, headers);
406 procheader_header_array_destroy(headers);
408 gtk_text_buffer_get_end_iter(buffer, &iter);
409 textview->body_pos = gtk_text_iter_get_offset(&iter);
410 if (!mimeinfo->main) {
411 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
415 if (mimeinfo->type == MIMETYPE_MULTIPART)
416 textview_add_parts(textview, mimeinfo);
418 textview_write_body(textview, mimeinfo, charset);
422 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
425 GtkTextBuffer *buffer;
428 const gchar *charset = NULL;
429 GPtrArray *headers = NULL;
433 g_return_if_fail(mimeinfo != NULL);
434 text = GTK_TEXT_VIEW(textview->text);
435 buffer = gtk_text_view_get_buffer(text);
436 charcount = gtk_text_buffer_get_char_count(buffer);
437 gtk_text_buffer_get_end_iter(buffer, &iter);
439 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
441 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
444 fp = fopen(mimeinfo->filename, "rb");
445 fseek(fp, mimeinfo->offset, SEEK_SET);
446 headers = textview_scan_header(textview, fp);
449 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
450 textview_show_header(textview, headers);
451 procheader_header_array_destroy(headers);
457 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
459 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
461 g_snprintf(buf, sizeof(buf), "\n[%s %s/%s (%d bytes)]\n",
463 procmime_get_type_str(mimeinfo->type),
464 mimeinfo->subtype, mimeinfo->length);
466 g_snprintf(buf, sizeof(buf), "\n[%s/%s (%d bytes)]\n",
467 procmime_get_type_str(mimeinfo->type),
468 mimeinfo->subtype, mimeinfo->length);
470 if (mimeinfo->type != MIMETYPE_TEXT) {
471 gtk_text_buffer_insert(buffer, &iter, buf, -1);
473 if (prefs_common.display_header && (charcount > 0))
474 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
475 if (textview->messageview->forced_charset)
476 charset = textview->messageview->forced_charset;
477 else if (prefs_common.force_charset)
478 charset = prefs_common.force_charset;
480 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
482 textview_write_body(textview, mimeinfo, charset);
487 static gboolean add_parts_func(GNode *node, gpointer data)
489 MimeInfo *mimeinfo = (MimeInfo *) node->data;
490 TextView *textview = (TextView *) data;
492 g_return_val_if_fail(mimeinfo != NULL, FALSE);
494 textview_add_part(textview, mimeinfo);
499 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
501 g_return_if_fail(mimeinfo != NULL);
503 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, add_parts_func, textview);
507 static void recursive_add_parts(TextView *textview, GNode *node)
512 mimeinfo = (MimeInfo *) node->data;
514 textview_add_part(textview, mimeinfo);
516 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
517 (mimeinfo->type != MIMETYPE_MESSAGE))
520 if (strcasecmp(mimeinfo->subtype, "alternative") == 0) {
521 GNode * prefered_body;
529 prefered_body = NULL;
532 for(iter = g_node_first_child(node) ; iter != NULL ;
533 iter = g_node_next_sibling(iter)) {
538 submime = (MimeInfo *) iter->data;
539 if (submime->type == MIMETYPE_TEXT)
542 if (submime->subtype != NULL) {
543 if (strcasecmp(submime->subtype, "plain") == 0)
547 if (score > prefered_score) {
548 prefered_score = score;
549 prefered_body = iter;
553 if (prefered_body != NULL) {
554 recursive_add_parts(textview, prefered_body);
558 for(iter = g_node_first_child(node) ; iter != NULL ;
559 iter = g_node_next_sibling(iter)) {
560 recursive_add_parts(textview, iter);
565 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
567 g_return_if_fail(mimeinfo != NULL);
569 recursive_add_parts(textview, mimeinfo->node);
572 #define TEXT_INSERT(str) \
573 gtk_text_buffer_insert(buffer, &iter, str, -1)
575 void textview_show_error(TextView *textview)
578 GtkTextBuffer *buffer;
581 textview_set_font(textview, NULL);
582 textview_clear(textview);
584 text = GTK_TEXT_VIEW(textview->text);
585 buffer = gtk_text_view_get_buffer(text);
586 gtk_text_buffer_get_start_iter(buffer, &iter);
588 TEXT_INSERT(_("This message can't be displayed.\n"));
592 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
595 GtkTextBuffer *buffer;
598 if (!partinfo) return;
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(_("The following can be performed on this part by "));
608 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
610 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
611 TEXT_INSERT(_(" To display as text select 'Display as text' "));
612 TEXT_INSERT(_("(Shortcut key: 't')\n"));
613 TEXT_INSERT(_(" To open with an external program select 'Open' "));
614 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
615 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
616 TEXT_INSERT(_("mouse button),\n"));
617 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
623 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
624 const gchar *charset)
630 conv = conv_code_converter_new(charset);
632 textview->is_in_signature = FALSE;
634 if(mimeinfo->encoding_type != ENC_BINARY &&
635 mimeinfo->encoding_type != ENC_7BIT &&
636 mimeinfo->encoding_type != ENC_8BIT)
637 procmime_decode_content(mimeinfo);
639 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
642 filename = procmime_get_tmp_file_name(mimeinfo);
643 if (procmime_get_part(filename, mimeinfo) == 0) {
644 tmpfp = fopen(filename, "rb");
645 textview_show_html(textview, tmpfp, conv);
650 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
653 filename = procmime_get_tmp_file_name(mimeinfo);
654 if (procmime_get_part(filename, mimeinfo) == 0) {
655 tmpfp = fopen(filename, "rb");
656 textview_show_ertf(textview, tmpfp, conv);
662 tmpfp = fopen(mimeinfo->filename, "rb");
663 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
664 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
665 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
666 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
667 textview_write_line(textview, buf, conv);
671 conv_code_converter_destroy(conv);
674 static void textview_show_html(TextView *textview, FILE *fp,
680 parser = html_parser_new(fp, conv);
681 g_return_if_fail(parser != NULL);
683 while ((str = html_parse(parser)) != NULL) {
684 if (parser->state == HTML_HREF) {
685 /* first time : get and copy the URL */
686 if (parser->href == NULL) {
687 /* ALF - the sylpheed html parser returns an empty string,
688 * if still inside an <a>, but already parsed past HREF */
689 str = strtok(str, " ");
691 parser->href = strdup(str);
692 /* the URL may (or not) be followed by the
694 str = strtok(NULL, "");
698 textview_write_link(textview, str, parser->href, NULL);
700 textview_write_line(textview, str, NULL);
702 html_parser_destroy(parser);
705 static void textview_show_ertf(TextView *textview, FILE *fp,
711 parser = ertf_parser_new(fp, conv);
712 g_return_if_fail(parser != NULL);
714 while ((str = ertf_parse(parser)) != NULL) {
715 textview_write_line(textview, str, NULL);
718 ertf_parser_destroy(parser);
721 /* get_uri_part() - retrieves a URI starting from scanpos.
722 Returns TRUE if succesful */
723 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
724 const gchar **bp, const gchar **ep)
728 g_return_val_if_fail(start != NULL, FALSE);
729 g_return_val_if_fail(scanpos != NULL, FALSE);
730 g_return_val_if_fail(bp != NULL, FALSE);
731 g_return_val_if_fail(ep != NULL, FALSE);
735 /* find end point of URI */
736 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
737 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
741 /* no punctuation at end of string */
743 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
744 * should pass some URI type to this function and decide on that whether
745 * to perform punctuation stripping */
747 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
749 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
759 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
761 return g_strndup(bp, ep - bp);
764 /* valid mail address characters */
765 #define IS_RFC822_CHAR(ch) \
770 !strchr("(),;<>\"", (ch)))
772 /* alphabet and number within 7bit ASCII */
773 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
774 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
776 static GHashTable *create_domain_tab(void)
778 static const gchar *toplvl_domains [] = {
780 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
781 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
782 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
783 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
784 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
785 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
786 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
787 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
788 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
789 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
790 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
791 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
792 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
793 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
794 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
795 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
796 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
797 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
798 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
799 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
800 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
801 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
802 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
803 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
804 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
805 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
808 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
810 g_return_val_if_fail(htab, NULL);
811 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
812 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
816 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
818 const gint MAX_LVL_DOM_NAME_LEN = 6;
819 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
820 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
823 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
826 for (p = buf; p < m && first < last; *p++ = *first++)
830 return g_hash_table_lookup(tab, buf) != NULL;
833 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
834 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
835 const gchar **bp, const gchar **ep)
837 /* more complex than the uri part because we need to scan back and forward starting from
838 * the scan position. */
839 gboolean result = FALSE;
840 const gchar *bp_ = NULL;
841 const gchar *ep_ = NULL;
842 static GHashTable *dom_tab;
843 const gchar *last_dot = NULL;
844 const gchar *prelast_dot = NULL;
845 const gchar *last_tld_char = NULL;
847 /* the informative part of the email address (describing the name
848 * of the email address owner) may contain quoted parts. the
849 * closure stack stores the last encountered quotes. */
850 gchar closure_stack[128];
851 gchar *ptr = closure_stack;
853 g_return_val_if_fail(start != NULL, FALSE);
854 g_return_val_if_fail(scanpos != NULL, FALSE);
855 g_return_val_if_fail(bp != NULL, FALSE);
856 g_return_val_if_fail(ep != NULL, FALSE);
859 dom_tab = create_domain_tab();
860 g_return_val_if_fail(dom_tab, FALSE);
862 /* scan start of address */
863 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
866 /* TODO: should start with an alnum? */
868 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
871 if (bp_ != scanpos) {
872 /* scan end of address */
873 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
875 prelast_dot = last_dot;
877 if (*(last_dot + 1) == '.') {
878 if (prelast_dot == NULL)
880 last_dot = prelast_dot;
885 /* TODO: really should terminate with an alnum? */
886 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
890 if (last_dot == NULL)
893 last_dot = prelast_dot;
894 if (last_dot == NULL || (scanpos + 1 >= last_dot))
898 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
899 if (*last_tld_char == '?')
902 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
909 if (!result) return FALSE;
911 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
912 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
915 /* see if this is <bracketed>; in this case we also scan for the informative part. */
916 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
919 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
920 #define IN_STACK() (ptr > closure_stack)
921 /* has underrun check */
922 #define POP_STACK() if(IN_STACK()) --ptr
923 /* has overrun check */
924 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
925 /* has underrun check */
926 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
930 /* scan for the informative part. */
931 for (bp_ -= 2; bp_ >= start; bp_--) {
932 /* if closure on the stack keep scanning */
933 if (PEEK_STACK() == *bp_) {
937 if (*bp_ == '\'' || *bp_ == '"') {
942 /* if nothing in the closure stack, do the special conditions
943 * the following if..else expression simply checks whether
944 * a token is acceptable. if not acceptable, the clause
945 * should terminate the loop with a 'break' */
948 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
949 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
950 /* hyphens are allowed, but only in
952 } else if (!ispunct(*bp_)) {
953 /* but anything not being a punctiation
956 break; /* anything else is rejected */
969 /* scan forward (should start with an alnum) */
970 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
980 #undef IS_RFC822_CHAR
982 static gchar *make_email_string(const gchar *bp, const gchar *ep)
984 /* returns a mailto: URI; mailto: is also used to detect the
985 * uri type later on in the button_pressed signal handler */
989 tmp = g_strndup(bp, ep - bp);
990 result = g_strconcat("mailto:", tmp, NULL);
996 #define ADD_TXT_POS(bp_, ep_, pti_) \
997 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
999 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1000 last->next = NULL; \
1002 g_warning("alloc error scanning URIs\n"); \
1003 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1009 /* textview_make_clickable_parts() - colorizes clickable parts */
1010 static void textview_make_clickable_parts(TextView *textview,
1011 const gchar *fg_tag,
1012 const gchar *uri_tag,
1013 const gchar *linebuf)
1015 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1016 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1019 /* parse table - in order of priority */
1021 const gchar *needle; /* token */
1023 /* token search function */
1024 gchar *(*search) (const gchar *haystack,
1025 const gchar *needle);
1026 /* part parsing function */
1027 gboolean (*parse) (const gchar *start,
1028 const gchar *scanpos,
1031 /* part to URI function */
1032 gchar *(*build_uri) (const gchar *bp,
1036 static struct table parser[] = {
1037 {"http://", strcasestr, get_uri_part, make_uri_string},
1038 {"https://", strcasestr, get_uri_part, make_uri_string},
1039 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1040 {"www.", strcasestr, get_uri_part, make_uri_string},
1041 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1042 {"@", strcasestr, get_email_part, make_email_string}
1044 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1047 const gchar *walk, *bp, *ep;
1050 const gchar *bp, *ep; /* text position */
1051 gint pti; /* index in parse table */
1052 struct txtpos *next; /* next */
1053 } head = {NULL, NULL, 0, NULL}, *last = &head;
1055 gtk_text_buffer_get_end_iter(buffer, &iter);
1057 /* parse for clickable parts, and build a list of begin and end positions */
1058 for (walk = linebuf, n = 0;;) {
1059 gint last_index = PARSE_ELEMS;
1060 gchar *scanpos = NULL;
1062 /* FIXME: this looks phony. scanning for anything in the parse table */
1063 for (n = 0; n < PARSE_ELEMS; n++) {
1066 tmp = parser[n].search(walk, parser[n].needle);
1068 if (scanpos == NULL || tmp < scanpos) {
1076 /* check if URI can be parsed */
1077 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1078 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1079 ADD_TXT_POS(bp, ep, last_index);
1083 strlen(parser[last_index].needle);
1088 /* colorize this line */
1090 const gchar *normal_text = linebuf;
1093 for (last = head.next; last != NULL;
1094 normal_text = last->ep, last = last->next) {
1097 uri = g_new(RemoteURI, 1);
1098 if (last->bp - normal_text > 0)
1099 gtk_text_buffer_insert_with_tags_by_name
1102 last->bp - normal_text,
1104 uri->uri = parser[last->pti].build_uri(last->bp,
1106 uri->start = gtk_text_iter_get_offset(&iter);
1107 gtk_text_buffer_insert_with_tags_by_name
1109 last->bp, last->ep - last->bp,
1111 uri->end = gtk_text_iter_get_offset(&iter);
1112 textview->uri_list =
1113 g_slist_append(textview->uri_list, uri);
1117 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1121 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1129 static void textview_write_line(TextView *textview, const gchar *str,
1130 CodeConverter *conv)
1133 GtkTextBuffer *buffer;
1135 gchar buf[BUFFSIZE];
1137 gint quotelevel = -1;
1138 gchar quote_tag_str[10];
1140 text = GTK_TEXT_VIEW(textview->text);
1141 buffer = gtk_text_view_get_buffer(text);
1142 gtk_text_buffer_get_end_iter(buffer, &iter);
1147 if (textview->text_is_mb)
1148 conv_localetodisp(buf, sizeof(buf), str);
1150 strncpy2(buf, str, sizeof(buf));
1151 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1152 conv_localetodisp(buf, sizeof(buf), str);
1153 else if (textview->text_is_mb)
1154 conv_unreadable_locale(buf);
1157 strncpy2(buf, str, sizeof(buf));
1158 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1159 conv_localetodisp(buf, sizeof(buf), str);
1163 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1166 /* change color of quotation
1167 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1168 Up to 3 levels of quotations are detected, and each
1169 level is colored using a different color. */
1170 if (prefs_common.enable_color
1171 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1172 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1174 /* set up the correct foreground color */
1175 if (quotelevel > 2) {
1176 /* recycle colors */
1177 if (prefs_common.recycle_quote_colors)
1184 if (quotelevel == -1)
1187 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1188 "quote%d", quotelevel);
1189 fg_color = quote_tag_str;
1192 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1193 fg_color = "signature";
1194 textview->is_in_signature = TRUE;
1197 if (prefs_common.enable_color)
1198 textview_make_clickable_parts(textview, fg_color, "link", buf);
1200 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1203 void textview_write_link(TextView *textview, const gchar *str,
1204 const gchar *uri, CodeConverter *conv)
1206 GdkColor *link_color = NULL;
1208 GtkTextBuffer *buffer;
1210 gchar buf[BUFFSIZE];
1217 text = GTK_TEXT_VIEW(textview->text);
1218 buffer = gtk_text_view_get_buffer(text);
1219 gtk_text_buffer_get_end_iter(buffer, &iter);
1224 if (textview->text_is_mb)
1225 conv_localetodisp(buf, sizeof(buf), str);
1227 strncpy2(buf, str, sizeof(buf));
1228 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1229 conv_localetodisp(buf, sizeof(buf), str);
1230 else if (textview->text_is_mb)
1231 conv_unreadable_locale(buf);
1234 strncpy2(buf, str, sizeof(buf));
1235 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1236 conv_localetodisp(buf, sizeof(buf), str);
1241 gtk_text_buffer_get_end_iter(buffer, &iter);
1243 for (bufp = buf; isspace(*bufp); bufp++)
1244 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1246 if (prefs_common.enable_color) {
1247 link_color = &uri_color;
1249 r_uri = g_new(RemoteURI, 1);
1250 r_uri->uri = g_strdup(uri);
1251 r_uri->start = gtk_text_iter_get_offset(&iter);
1252 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1254 r_uri->end = gtk_text_iter_get_offset(&iter);
1255 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1258 void textview_clear(TextView *textview)
1260 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1261 GtkTextBuffer *buffer;
1263 buffer = gtk_text_view_get_buffer(text);
1264 gtk_text_buffer_set_text(buffer, "\0", -1);
1266 textview_uri_list_remove_all(textview->uri_list);
1267 textview->uri_list = NULL;
1269 textview->body_pos = 0;
1272 void textview_destroy(TextView *textview)
1274 textview_uri_list_remove_all(textview->uri_list);
1275 textview->uri_list = NULL;
1280 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1282 textview->show_all_headers = all_headers;
1285 void textview_set_font(TextView *textview, const gchar *codeset)
1287 if (prefs_common.textfont) {
1288 PangoFontDescription *font_desc = NULL;
1290 if (prefs_common.textfont)
1291 font_desc = pango_font_description_from_string
1292 (prefs_common.textfont);
1294 gtk_widget_modify_font(textview->text, font_desc);
1295 pango_font_description_free(font_desc);
1298 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1299 prefs_common.line_space / 2);
1300 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1301 prefs_common.line_space / 2);
1302 if (prefs_common.head_space) {
1303 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1305 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1309 void textview_set_text(TextView *textview, const gchar *text)
1312 GtkTextBuffer *buffer;
1314 g_return_if_fail(textview != NULL);
1315 g_return_if_fail(text != NULL);
1317 textview_clear(textview);
1319 view = GTK_TEXT_VIEW(textview->text);
1320 buffer = gtk_text_view_get_buffer(view);
1321 gtk_text_buffer_set_text(buffer, text, strlen(text));
1337 H_ORGANIZATION = 11,
1340 void textview_set_position(TextView *textview, gint pos)
1342 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1343 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1346 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1347 gtk_text_buffer_place_cursor(buffer, &iter);
1350 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1352 gchar buf[BUFFSIZE];
1353 GPtrArray *headers, *sorted_headers;
1354 GSList *disphdr_list;
1358 g_return_val_if_fail(fp != NULL, NULL);
1360 if (textview->show_all_headers)
1361 return procheader_get_header_array_asis(fp);
1363 if (!prefs_common.display_header) {
1364 while (fgets(buf, sizeof(buf), fp) != NULL)
1365 if (buf[0] == '\r' || buf[0] == '\n') break;
1369 headers = procheader_get_header_array_asis(fp);
1371 sorted_headers = g_ptr_array_new();
1373 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1374 disphdr_list = disphdr_list->next) {
1375 DisplayHeaderProp *dp =
1376 (DisplayHeaderProp *)disphdr_list->data;
1378 for (i = 0; i < headers->len; i++) {
1379 header = g_ptr_array_index(headers, i);
1381 if (procheader_headername_equal(header->name,
1384 procheader_header_free(header);
1386 g_ptr_array_add(sorted_headers, header);
1388 g_ptr_array_remove_index(headers, i);
1394 if (prefs_common.show_other_header) {
1395 for (i = 0; i < headers->len; i++) {
1396 header = g_ptr_array_index(headers, i);
1397 g_ptr_array_add(sorted_headers, header);
1399 g_ptr_array_free(headers, TRUE);
1401 procheader_header_array_destroy(headers);
1404 return sorted_headers;
1407 static void textview_show_header(TextView *textview, GPtrArray *headers)
1409 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1410 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1415 g_return_if_fail(headers != NULL);
1417 for (i = 0; i < headers->len; i++) {
1418 header = g_ptr_array_index(headers, i);
1419 g_return_if_fail(header->name != NULL);
1421 gtk_text_buffer_get_end_iter (buffer, &iter);
1422 gtk_text_buffer_insert_with_tags_by_name
1423 (buffer, &iter, header->name, -1,
1424 "header_title", "header", NULL);
1425 if (header->name[strlen(header->name) - 1] != ' ')
1428 gtk_stext_insert(text, textview->boldfont,
1429 NULL, NULL, " ", 1);
1431 gtk_text_buffer_insert_with_tags_by_name
1432 (buffer, &iter, " ", 1,
1433 "header_title", "header", NULL);
1436 if (procheader_headername_equal(header->name, "Subject") ||
1437 procheader_headername_equal(header->name, "From") ||
1438 procheader_headername_equal(header->name, "To") ||
1439 procheader_headername_equal(header->name, "Cc"))
1440 unfold_line(header->body);
1444 if (textview->text_is_mb == TRUE)
1445 conv_unreadable_locale(header->body);
1448 if (prefs_common.enable_color &&
1449 (procheader_headername_equal(header->name, "X-Mailer") ||
1450 procheader_headername_equal(header->name,
1452 strstr(header->body, "Sylpheed") != NULL) {
1453 gtk_text_buffer_get_end_iter (buffer, &iter);
1454 gtk_text_buffer_insert_with_tags_by_name
1455 (buffer, &iter, header->body, -1,
1456 "header", "emphasis", NULL);
1457 } else if (prefs_common.enable_color) {
1458 textview_make_clickable_parts(textview, "header", "link",
1461 textview_make_clickable_parts(textview, "header", NULL,
1464 gtk_text_buffer_get_end_iter (buffer, &iter);
1465 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1470 gboolean textview_search_string(TextView *textview, const gchar *str,
1473 #warning FIXME_GTK2 /* currently, these search functions ignores case_sens */
1475 GtkSText *text = GTK_STEXT(textview->text);
1479 g_return_val_if_fail(str != NULL, FALSE);
1481 len = get_mbs_len(str);
1482 g_return_val_if_fail(len >= 0, FALSE);
1484 pos = textview->cur_pos;
1485 if (pos < textview->body_pos)
1486 pos = textview->body_pos;
1488 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1489 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1490 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1491 textview_set_position(textview, pos + len);
1497 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1498 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1500 GtkTextIter iter, start, end, *pos;
1502 gint insert_offset, selbound_offset;
1504 /* reset selection */
1505 mark = gtk_text_buffer_get_mark(buffer, "insert");
1506 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1507 insert_offset = gtk_text_iter_get_offset(&start);
1508 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1509 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1510 selbound_offset = gtk_text_iter_get_offset(&end);
1512 pos = insert_offset > selbound_offset ? &start : &end;
1513 gtk_text_buffer_place_cursor(buffer, pos);
1516 mark = gtk_text_buffer_get_insert(buffer);
1517 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1518 found = gtk_text_iter_forward_search(&iter, str,
1519 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1520 &start, &end, NULL);
1522 gtk_text_buffer_place_cursor(buffer, &start);
1523 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", &end);
1524 mark = gtk_text_buffer_get_mark(buffer, "insert");
1525 gtk_text_view_scroll_mark_onscreen(text, mark);
1532 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1537 GtkSText *text = GTK_STEXT(textview->text);
1542 gboolean found = FALSE;
1544 g_return_val_if_fail(str != NULL, FALSE);
1546 wcs = strdup_mbstowcs(str);
1547 g_return_val_if_fail(wcs != NULL, FALSE);
1549 pos = textview->cur_pos;
1550 text_len = gtk_stext_get_length(text);
1551 if (text_len - textview->body_pos < len) {
1555 if (pos <= textview->body_pos || text_len - pos < len)
1556 pos = text_len - len;
1558 for (; pos >= textview->body_pos; pos--) {
1559 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1561 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1562 gtk_editable_select_region(GTK_EDITABLE(text),
1564 textview_set_position(textview, pos - 1);
1568 if (pos == textview->body_pos) break;
1574 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1575 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1577 GtkTextIter iter, start, end, *pos;
1579 gint insert_offset, selbound_offset;
1581 /* reset selection */
1582 mark = gtk_text_buffer_get_mark(buffer, "insert");
1583 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1584 insert_offset = gtk_text_iter_get_offset(&start);
1585 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1586 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1587 selbound_offset = gtk_text_iter_get_offset(&end);
1589 pos = insert_offset < selbound_offset ? &start : &end;
1590 gtk_text_buffer_place_cursor(buffer, pos);
1593 mark = gtk_text_buffer_get_insert(buffer);
1594 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1595 found = gtk_text_iter_backward_search(&iter, str,
1596 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1597 &start, &end, NULL);
1599 gtk_text_buffer_place_cursor(buffer, &end);
1600 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", &start);
1601 mark = gtk_text_buffer_get_mark(buffer, "insert");
1602 gtk_text_view_scroll_mark_onscreen(text, mark);
1609 void textview_scroll_one_line(TextView *textview, gboolean up)
1611 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1612 GtkAdjustment *vadj = text->vadjustment;
1615 if (prefs_common.enable_smooth_scroll) {
1616 textview_smooth_scroll_one_line(textview, up);
1621 upper = vadj->upper - vadj->page_size;
1622 if (vadj->value < upper) {
1624 vadj->step_increment * 4;
1626 MIN(vadj->value, upper);
1627 g_signal_emit_by_name(G_OBJECT(vadj),
1628 "value_changed", 0);
1631 if (vadj->value > 0.0) {
1633 vadj->step_increment * 4;
1635 MAX(vadj->value, 0.0);
1636 g_signal_emit_by_name(G_OBJECT(vadj),
1637 "value_changed", 0);
1642 gboolean textview_scroll_page(TextView *textview, gboolean up)
1644 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1645 GtkAdjustment *vadj = text->vadjustment;
1649 if (prefs_common.enable_smooth_scroll)
1650 return textview_smooth_scroll_page(textview, up);
1652 if (prefs_common.scroll_halfpage)
1653 page_incr = vadj->page_increment / 2;
1655 page_incr = vadj->page_increment;
1658 upper = vadj->upper - vadj->page_size;
1659 if (vadj->value < upper) {
1660 vadj->value += page_incr;
1661 vadj->value = MIN(vadj->value, upper);
1662 g_signal_emit_by_name(G_OBJECT(vadj),
1663 "value_changed", 0);
1667 if (vadj->value > 0.0) {
1668 vadj->value -= page_incr;
1669 vadj->value = MAX(vadj->value, 0.0);
1670 g_signal_emit_by_name(G_OBJECT(vadj),
1671 "value_changed", 0);
1679 static void textview_smooth_scroll_do(TextView *textview,
1680 gfloat old_value, gfloat last_value,
1683 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1684 GtkAdjustment *vadj = text->vadjustment;
1689 if (old_value < last_value) {
1690 change_value = last_value - old_value;
1693 change_value = old_value - last_value;
1698 /* gdk_key_repeat_disable(); */
1700 for (i = step; i <= change_value; i += step) {
1701 vadj->value = old_value + (up ? -i : i);
1702 g_signal_emit_by_name(G_OBJECT(vadj),
1703 "value_changed", 0);
1706 vadj->value = last_value;
1707 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1710 /* gdk_key_repeat_restore(); */
1713 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1715 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1716 GtkAdjustment *vadj = text->vadjustment;
1722 upper = vadj->upper - vadj->page_size;
1723 if (vadj->value < upper) {
1724 old_value = vadj->value;
1725 last_value = vadj->value +
1726 vadj->step_increment * 4;
1727 last_value = MIN(last_value, upper);
1729 textview_smooth_scroll_do(textview, old_value,
1731 prefs_common.scroll_step);
1734 if (vadj->value > 0.0) {
1735 old_value = vadj->value;
1736 last_value = vadj->value -
1737 vadj->step_increment * 4;
1738 last_value = MAX(last_value, 0.0);
1740 textview_smooth_scroll_do(textview, old_value,
1742 prefs_common.scroll_step);
1747 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1749 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1750 GtkAdjustment *vadj = text->vadjustment;
1756 if (prefs_common.scroll_halfpage)
1757 page_incr = vadj->page_increment / 2;
1759 page_incr = vadj->page_increment;
1762 upper = vadj->upper - vadj->page_size;
1763 if (vadj->value < upper) {
1764 old_value = vadj->value;
1765 last_value = vadj->value + page_incr;
1766 last_value = MIN(last_value, upper);
1768 textview_smooth_scroll_do(textview, old_value,
1770 prefs_common.scroll_step);
1774 if (vadj->value > 0.0) {
1775 old_value = vadj->value;
1776 last_value = vadj->value - page_incr;
1777 last_value = MAX(last_value, 0.0);
1779 textview_smooth_scroll_do(textview, old_value,
1781 prefs_common.scroll_step);
1791 #define KEY_PRESS_EVENT_STOP() \
1792 if (gtk_signal_n_emissions_by_name \
1793 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1794 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1795 "key_press_event"); \
1798 #define KEY_PRESS_EVENT_STOP() \
1799 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1803 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1806 SummaryView *summaryview = NULL;
1807 MessageView *messageview = textview->messageview;
1809 if (!event) return FALSE;
1810 if (messageview->mainwin)
1811 summaryview = messageview->mainwin->summaryview;
1813 switch (event->keyval) {
1828 summary_pass_key_press_event(summaryview, event);
1830 textview_scroll_page(textview, FALSE);
1833 textview_scroll_page(textview, TRUE);
1836 textview_scroll_one_line(textview,
1837 (event->state & GDK_MOD1_MASK) != 0);
1841 summary_pass_key_press_event(summaryview, event);
1846 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1847 KEY_PRESS_EVENT_STOP();
1848 mimeview_pass_key_press_event(messageview->mimeview,
1852 /* possible fall through */
1855 event->window != messageview->mainwin->window->window) {
1856 GdkEventKey tmpev = *event;
1858 tmpev.window = messageview->mainwin->window->window;
1859 KEY_PRESS_EVENT_STOP();
1860 gtk_widget_event(messageview->mainwin->window,
1861 (GdkEvent *)&tmpev);
1869 static gint show_url_timeout_cb(gpointer data)
1871 TextView *textview = (TextView *)data;
1873 if (textview->messageview->mainwin)
1874 if (textview->show_url_msgid)
1875 gtk_statusbar_remove(GTK_STATUSBAR(
1876 textview->messageview->mainwin->statusbar),
1877 textview->messageview->mainwin->folderview_cid,
1878 textview->show_url_msgid);
1883 *\brief Check to see if a web URL has been disguised as a different
1884 * URL (possible with HTML email).
1886 *\param uri The uri to check
1888 *\param textview The TextView the URL is contained in
1890 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1891 * it anyway, otherwise FALSE
1893 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1896 gboolean retval = TRUE;
1898 if (g_strncasecmp(uri->uri, "http:", 5) &&
1899 g_strncasecmp(uri->uri, "https:", 6) &&
1900 g_strncasecmp(uri->uri, "www.", 4))
1903 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1906 if (clicked_str == NULL)
1909 if (strcmp(clicked_str, uri->uri) &&
1910 (!g_strncasecmp(clicked_str, "http:", 5) ||
1911 !g_strncasecmp(clicked_str, "https:", 6) ||
1912 !g_strncasecmp(clicked_str, "www.", 4))) {
1916 /* allow uri->uri == http://somewhere.com
1917 and clicked_str == somewhere.com */
1918 str = g_strconcat("http://", clicked_str, NULL);
1920 if (!g_strcasecmp(str, uri->uri))
1925 if (retval == FALSE) {
1929 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1930 "the apparent URL (%s). \n"
1932 uri->uri, clicked_str);
1933 resp = alertpanel(_("Warning"),
1939 if (resp == G_ALERTDEFAULT)
1942 g_free(clicked_str);
1946 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1947 GdkEvent *event, GtkTextIter *iter,
1950 GtkTextIter start_iter, end_iter;
1951 gint start_pos, end_pos;
1952 GdkEventButton *bevent;
1956 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
1957 && event->type != GDK_MOTION_NOTIFY)
1960 bevent = (GdkEventButton *) event;
1962 /* get start and end positions */
1964 if(!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) {
1965 debug_print("Can't find start.");
1968 start_pos = gtk_text_iter_get_offset(&start_iter);
1971 if(!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) {
1972 debug_print("Can't find end");
1975 end_pos = gtk_text_iter_get_offset(&end_iter);
1977 /* search current uri */
1978 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1979 RemoteURI *uri = (RemoteURI *)cur->data;
1981 if (start_pos != uri->start || end_pos != uri->end)
1984 trimmed_uri = trim_string(uri->uri, 60);
1985 /* hover or single click: display url in statusbar */
1986 if (event->type == GDK_MOTION_NOTIFY
1987 || (event->type == GDK_BUTTON_PRESS && bevent->button == 1)) {
1988 if (textview->messageview->mainwin) {
1989 if (textview->show_url_msgid) {
1990 gtk_timeout_remove(textview->show_url_timeout_tag);
1991 gtk_statusbar_remove(GTK_STATUSBAR(
1992 textview->messageview->mainwin->statusbar),
1993 textview->messageview->mainwin->folderview_cid,
1994 textview->show_url_msgid);
1995 textview->show_url_msgid = 0;
1997 textview->show_url_msgid = gtk_statusbar_push(
1998 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1999 textview->messageview->mainwin->folderview_cid,
2001 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
2002 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
2006 /* doubleclick: open compose / add address / browser */
2007 if ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) ||
2008 bevent->button == 2 || bevent->button == 3) {
2009 if (!g_strncasecmp(uri->uri, "mailto:", 7))
2010 if (bevent->button == 3) {
2011 gchar *fromname, *fromaddress;
2014 fromaddress = g_strdup(uri->uri + 7);
2015 /* Hiroyuki: please put this function in utils.c! */
2016 fromname = procheader_get_fromname(fromaddress);
2017 extract_address(fromaddress);
2018 g_message("adding from textview %s <%s>", fromname, fromaddress);
2019 /* Add to address book - Match */
2020 addressbook_add_contact( fromname, fromaddress, NULL );
2022 g_free(fromaddress);
2025 PrefsAccount *account = NULL;
2026 FolderItem *folder_item;
2028 if (textview->messageview && textview->messageview->mainwin
2029 && textview->messageview->mainwin->summaryview
2030 && textview->messageview->mainwin->summaryview->folder_item) {
2031 folder_item = textview->messageview->mainwin->summaryview->folder_item;
2032 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2033 account = account_find_from_id(folder_item->prefs->default_account);
2035 compose_new(account, uri->uri + 7, NULL);
2038 if (uri_security_check(uri, textview) == TRUE)
2039 open_uri(uri->uri, prefs_common.uri_cmd);
2042 g_free(trimmed_uri);
2048 static void textview_uri_list_remove_all(GSList *uri_list)
2052 for (cur = uri_list; cur != NULL; cur = cur->next) {
2054 g_free(((RemoteURI *)cur->data)->uri);
2059 g_slist_free(uri_list);