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