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,
152 static gint textview_button_pressed (GtkWidget *widget,
153 GdkEventButton *event,
155 static gint textview_button_released (GtkWidget *widget,
156 GdkEventButton *event,
159 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
160 GdkEvent *event, GtkTextIter *iter,
164 static void textview_uri_list_remove_all(GSList *uri_list);
166 static void textview_smooth_scroll_do (TextView *textview,
170 static void textview_smooth_scroll_one_line (TextView *textview,
172 static gboolean textview_smooth_scroll_page (TextView *textview,
176 TextView *textview_create(void)
180 GtkWidget *scrolledwin;
182 GtkTextBuffer *buffer;
183 GtkClipboard *clipboard;
184 PangoFontDescription *font_desc = NULL;
186 debug_print("Creating text view...\n");
187 textview = g_new0(TextView, 1);
189 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
190 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
191 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
192 gtk_widget_set_size_request(scrolledwin, prefs_common.mainview_width, -1);
194 /* create GtkSText widgets for single-byte and multi-byte character */
195 text = gtk_text_view_new();
196 gtk_widget_show(text);
197 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
198 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
200 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
201 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
202 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
204 gtk_widget_ensure_style(text);
206 if (prefs_common.normalfont)
207 font_desc = pango_font_description_from_string
208 (prefs_common.normalfont);
210 gtk_widget_modify_font(text, font_desc);
212 pango_font_description_free(font_desc);
214 gtk_widget_ref(scrolledwin);
216 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
218 g_signal_connect(G_OBJECT(text), "key_press_event",
219 G_CALLBACK(textview_key_pressed),
222 gtk_widget_show(scrolledwin);
224 vbox = gtk_vbox_new(FALSE, 0);
225 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
227 gtk_widget_show(vbox);
229 textview->vbox = vbox;
230 textview->scrolledwin = scrolledwin;
231 textview->text = text;
232 textview->uri_list = NULL;
233 textview->body_pos = 0;
234 textview->show_all_headers = FALSE;
235 textview->last_buttonpress = GDK_NOTHING;
236 textview->show_url_msgid = 0;
241 static void textview_create_tags(GtkTextView *text, TextView *textview)
243 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
246 gtk_text_buffer_create_tag(buffer, "header",
247 "pixels-above-lines", 0,
248 "pixels-above-lines-set", TRUE,
249 "pixels-below-lines", 0,
250 "pixels-below-lines-set", TRUE,
252 "left-margin-set", TRUE,
254 gtk_text_buffer_create_tag(buffer, "header_title",
255 "font", prefs_common.boldfont,
257 gtk_text_buffer_create_tag(buffer, "quote0",
258 "foreground-gdk", "e_colors[0],
260 gtk_text_buffer_create_tag(buffer, "quote1",
261 "foreground-gdk", "e_colors[1],
263 gtk_text_buffer_create_tag(buffer, "quote2",
264 "foreground-gdk", "e_colors[2],
266 gtk_text_buffer_create_tag(buffer, "emphasis",
267 "foreground-gdk", &emphasis_color,
269 gtk_text_buffer_create_tag(buffer, "signature",
270 "foreground-gdk", &signature_color,
272 tag = gtk_text_buffer_create_tag(buffer, "link",
273 "foreground-gdk", &uri_color,
276 gtk_text_buffer_create_tag(buffer, "good-signature",
277 "foreground-gdk", &good_sig_color,
279 gtk_text_buffer_create_tag(buffer, "bad-signature",
280 "foreground-gdk", &bad_sig_color,
282 gtk_text_buffer_create_tag(buffer, "nocheck-signature",
283 "foreground-gdk", &nocheck_sig_color,
285 #endif /*USE_GPGME */
287 g_signal_connect(G_OBJECT(tag), "event",
288 G_CALLBACK(textview_uri_button_pressed), textview);
291 void textview_init(TextView *textview)
295 gtkut_widget_disable_theme_engine(textview->text);
297 textview_update_message_colors();
298 textview_set_all_headers(textview, FALSE);
299 textview_set_font(textview, NULL);
301 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
304 void textview_update_message_colors(void)
306 GdkColor black = {0, 0, 0, 0};
308 if (prefs_common.enable_color) {
309 /* grab the quote colors, converting from an int to a GdkColor */
310 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
312 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
314 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
316 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
318 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
321 quote_colors[0] = quote_colors[1] = quote_colors[2] =
322 uri_color = emphasis_color = signature_color = black;
326 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
330 const gchar *charset = NULL;
332 if ((fp = fopen(file, "rb")) == NULL) {
333 FILE_OP_ERROR(file, "fopen");
337 if (textview->messageview->forced_charset)
338 charset = textview->messageview->forced_charset;
339 else if (prefs_common.force_charset)
340 charset = prefs_common.force_charset;
342 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
344 textview_set_font(textview, charset);
345 textview_clear(textview);
349 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) perror("fseek");
350 headers = textview_scan_header(textview, fp);
352 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
353 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
356 textview_show_header(textview, headers);
357 procheader_header_array_destroy(headers);
359 gtk_text_buffer_get_end_iter(buffer, &iter);
360 textview->body_pos = gtk_text_iter_get_offset(&iter);
363 textview_add_parts(textview, mimeinfo);
367 textview_set_position(textview, 0);
370 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
373 const gchar *charset = NULL;
375 g_return_if_fail(mimeinfo != NULL);
376 g_return_if_fail(fp != NULL);
378 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
379 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
380 textview_clear(textview);
381 textview_add_parts(textview, mimeinfo);
385 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
388 headers = textview_scan_header(textview, fp);
390 if (textview->messageview->forced_charset)
391 charset = textview->messageview->forced_charset;
392 else if (prefs_common.force_charset)
393 charset = prefs_common.force_charset;
395 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
397 textview_set_font(textview, charset);
399 textview_clear(textview);
403 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
404 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
407 textview_show_header(textview, headers);
408 procheader_header_array_destroy(headers);
410 gtk_text_buffer_get_end_iter(buffer, &iter);
411 textview->body_pos = gtk_text_iter_get_offset(&iter);
412 if (!mimeinfo->main) {
413 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
417 if (mimeinfo->type == MIMETYPE_MULTIPART)
418 textview_add_parts(textview, mimeinfo);
420 textview_write_body(textview, mimeinfo, charset);
424 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
427 GtkTextBuffer *buffer;
430 const gchar *charset = NULL;
431 GPtrArray *headers = NULL;
435 g_return_if_fail(mimeinfo != NULL);
436 text = GTK_TEXT_VIEW(textview->text);
437 buffer = gtk_text_view_get_buffer(text);
438 charcount = gtk_text_buffer_get_char_count(buffer);
439 gtk_text_buffer_get_end_iter(buffer, &iter);
441 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
443 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
446 fp = fopen(mimeinfo->filename, "rb");
447 fseek(fp, mimeinfo->offset, SEEK_SET);
448 headers = textview_scan_header(textview, fp);
451 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
452 textview_show_header(textview, headers);
453 procheader_header_array_destroy(headers);
459 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
461 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
463 g_snprintf(buf, sizeof(buf), "\n[%s %s/%s (%d bytes)]\n",
465 procmime_get_type_str(mimeinfo->type),
466 mimeinfo->subtype, mimeinfo->length);
468 g_snprintf(buf, sizeof(buf), "\n[%s/%s (%d bytes)]\n",
469 procmime_get_type_str(mimeinfo->type),
470 mimeinfo->subtype, mimeinfo->length);
472 if (mimeinfo->type != MIMETYPE_TEXT) {
473 gtk_text_buffer_insert(buffer, &iter, buf, -1);
475 if (prefs_common.display_header && (charcount > 0))
476 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
477 if (textview->messageview->forced_charset)
478 charset = textview->messageview->forced_charset;
479 else if (prefs_common.force_charset)
480 charset = prefs_common.force_charset;
482 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
484 textview_write_body(textview, mimeinfo, charset);
489 static gboolean add_parts_func(GNode *node, gpointer data)
491 MimeInfo *mimeinfo = (MimeInfo *) node->data;
492 TextView *textview = (TextView *) data;
494 g_return_val_if_fail(mimeinfo != NULL, FALSE);
496 textview_add_part(textview, mimeinfo);
501 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
503 g_return_if_fail(mimeinfo != NULL);
505 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, add_parts_func, textview);
509 static void recursive_add_parts(TextView *textview, GNode *node)
514 mimeinfo = (MimeInfo *) node->data;
516 textview_add_part(textview, mimeinfo);
518 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
519 (mimeinfo->type != MIMETYPE_MESSAGE))
522 if (strcasecmp(mimeinfo->subtype, "alternative") == 0) {
523 GNode * prefered_body;
531 prefered_body = NULL;
534 for(iter = g_node_first_child(node) ; iter != NULL ;
535 iter = g_node_next_sibling(iter)) {
540 submime = (MimeInfo *) iter->data;
541 if (submime->type == MIMETYPE_TEXT)
544 if (submime->subtype != NULL) {
545 if (strcasecmp(submime->subtype, "plain") == 0)
549 if (score > prefered_score) {
550 prefered_score = score;
551 prefered_body = iter;
555 if (prefered_body != NULL) {
556 recursive_add_parts(textview, prefered_body);
560 for(iter = g_node_first_child(node) ; iter != NULL ;
561 iter = g_node_next_sibling(iter)) {
562 recursive_add_parts(textview, iter);
567 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
569 g_return_if_fail(mimeinfo != NULL);
571 recursive_add_parts(textview, mimeinfo->node);
574 #define TEXT_INSERT(str) \
575 gtk_text_buffer_insert(buffer, &iter, str, -1)
577 void textview_show_error(TextView *textview)
580 GtkTextBuffer *buffer;
583 textview_set_font(textview, NULL);
584 textview_clear(textview);
586 text = GTK_TEXT_VIEW(textview->text);
587 buffer = gtk_text_view_get_buffer(text);
588 gtk_text_buffer_get_start_iter(buffer, &iter);
590 TEXT_INSERT(_("This message can't be displayed.\n"));
594 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
597 GtkTextBuffer *buffer;
600 if (!partinfo) return;
602 textview_set_font(textview, NULL);
603 textview_clear(textview);
605 text = GTK_TEXT_VIEW(textview->text);
606 buffer = gtk_text_view_get_buffer(text);
607 gtk_text_buffer_get_start_iter(buffer, &iter);
609 TEXT_INSERT(_("The following can be performed on this part by "));
610 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
612 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
613 TEXT_INSERT(_(" To display as text select 'Display as text' "));
614 TEXT_INSERT(_("(Shortcut key: 't')\n"));
615 TEXT_INSERT(_(" To open with an external program select 'Open' "));
616 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
617 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
618 TEXT_INSERT(_("mouse button),\n"));
619 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
625 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
626 const gchar *charset)
632 conv = conv_code_converter_new(charset);
634 textview->is_in_signature = FALSE;
636 if(mimeinfo->encoding_type != ENC_BINARY &&
637 mimeinfo->encoding_type != ENC_7BIT &&
638 mimeinfo->encoding_type != ENC_8BIT)
639 procmime_decode_content(mimeinfo);
641 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
644 filename = procmime_get_tmp_file_name(mimeinfo);
645 if (procmime_get_part(filename, mimeinfo) == 0) {
646 tmpfp = fopen(filename, "rb");
647 textview_show_html(textview, tmpfp, conv);
652 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
655 filename = procmime_get_tmp_file_name(mimeinfo);
656 if (procmime_get_part(filename, mimeinfo) == 0) {
657 tmpfp = fopen(filename, "rb");
658 textview_show_ertf(textview, tmpfp, conv);
664 tmpfp = fopen(mimeinfo->filename, "rb");
665 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
666 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
667 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
668 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
669 textview_write_line(textview, buf, conv);
673 conv_code_converter_destroy(conv);
676 static void textview_show_html(TextView *textview, FILE *fp,
682 parser = html_parser_new(fp, conv);
683 g_return_if_fail(parser != NULL);
685 while ((str = html_parse(parser)) != NULL) {
686 if (parser->state == HTML_HREF) {
687 /* first time : get and copy the URL */
688 if (parser->href == NULL) {
689 /* ALF - the sylpheed html parser returns an empty string,
690 * if still inside an <a>, but already parsed past HREF */
691 str = strtok(str, " ");
693 parser->href = strdup(str);
694 /* the URL may (or not) be followed by the
696 str = strtok(NULL, "");
700 textview_write_link(textview, str, parser->href, NULL);
702 textview_write_line(textview, str, NULL);
704 html_parser_destroy(parser);
707 static void textview_show_ertf(TextView *textview, FILE *fp,
713 parser = ertf_parser_new(fp, conv);
714 g_return_if_fail(parser != NULL);
716 while ((str = ertf_parse(parser)) != NULL) {
717 textview_write_line(textview, str, NULL);
720 ertf_parser_destroy(parser);
723 /* get_uri_part() - retrieves a URI starting from scanpos.
724 Returns TRUE if succesful */
725 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
726 const gchar **bp, const gchar **ep)
730 g_return_val_if_fail(start != NULL, FALSE);
731 g_return_val_if_fail(scanpos != NULL, FALSE);
732 g_return_val_if_fail(bp != NULL, FALSE);
733 g_return_val_if_fail(ep != NULL, FALSE);
737 /* find end point of URI */
738 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
739 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
743 /* no punctuation at end of string */
745 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
746 * should pass some URI type to this function and decide on that whether
747 * to perform punctuation stripping */
749 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
751 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
761 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
763 return g_strndup(bp, ep - bp);
766 /* valid mail address characters */
767 #define IS_RFC822_CHAR(ch) \
772 !strchr("(),;<>\"", (ch)))
774 /* alphabet and number within 7bit ASCII */
775 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
776 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
778 static GHashTable *create_domain_tab(void)
780 static const gchar *toplvl_domains [] = {
782 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
783 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
784 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
785 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
786 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
787 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
788 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
789 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
790 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
791 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
792 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
793 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
794 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
795 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
796 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
797 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
798 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
799 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
800 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
801 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
802 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
803 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
804 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
805 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
806 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
807 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
810 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
812 g_return_val_if_fail(htab, NULL);
813 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
814 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
818 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
820 const gint MAX_LVL_DOM_NAME_LEN = 6;
821 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
822 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
825 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
828 for (p = buf; p < m && first < last; *p++ = *first++)
832 return g_hash_table_lookup(tab, buf) != NULL;
835 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
836 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
837 const gchar **bp, const gchar **ep)
839 /* more complex than the uri part because we need to scan back and forward starting from
840 * the scan position. */
841 gboolean result = FALSE;
842 const gchar *bp_ = NULL;
843 const gchar *ep_ = NULL;
844 static GHashTable *dom_tab;
845 const gchar *last_dot = NULL;
846 const gchar *prelast_dot = NULL;
847 const gchar *last_tld_char = NULL;
849 /* the informative part of the email address (describing the name
850 * of the email address owner) may contain quoted parts. the
851 * closure stack stores the last encountered quotes. */
852 gchar closure_stack[128];
853 gchar *ptr = closure_stack;
855 g_return_val_if_fail(start != NULL, FALSE);
856 g_return_val_if_fail(scanpos != NULL, FALSE);
857 g_return_val_if_fail(bp != NULL, FALSE);
858 g_return_val_if_fail(ep != NULL, FALSE);
861 dom_tab = create_domain_tab();
862 g_return_val_if_fail(dom_tab, FALSE);
864 /* scan start of address */
865 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
868 /* TODO: should start with an alnum? */
870 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
873 if (bp_ != scanpos) {
874 /* scan end of address */
875 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
877 prelast_dot = last_dot;
879 if (*(last_dot + 1) == '.') {
880 if (prelast_dot == NULL)
882 last_dot = prelast_dot;
887 /* TODO: really should terminate with an alnum? */
888 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
892 if (last_dot == NULL)
895 last_dot = prelast_dot;
896 if (last_dot == NULL || (scanpos + 1 >= last_dot))
900 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
901 if (*last_tld_char == '?')
904 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
911 if (!result) return FALSE;
913 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
914 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
917 /* see if this is <bracketed>; in this case we also scan for the informative part. */
918 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
921 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
922 #define IN_STACK() (ptr > closure_stack)
923 /* has underrun check */
924 #define POP_STACK() if(IN_STACK()) --ptr
925 /* has overrun check */
926 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
927 /* has underrun check */
928 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
932 /* scan for the informative part. */
933 for (bp_ -= 2; bp_ >= start; bp_--) {
934 /* if closure on the stack keep scanning */
935 if (PEEK_STACK() == *bp_) {
939 if (*bp_ == '\'' || *bp_ == '"') {
944 /* if nothing in the closure stack, do the special conditions
945 * the following if..else expression simply checks whether
946 * a token is acceptable. if not acceptable, the clause
947 * should terminate the loop with a 'break' */
950 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
951 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
952 /* hyphens are allowed, but only in
954 } else if (!ispunct(*bp_)) {
955 /* but anything not being a punctiation
958 break; /* anything else is rejected */
971 /* scan forward (should start with an alnum) */
972 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
982 #undef IS_RFC822_CHAR
984 static gchar *make_email_string(const gchar *bp, const gchar *ep)
986 /* returns a mailto: URI; mailto: is also used to detect the
987 * uri type later on in the button_pressed signal handler */
991 tmp = g_strndup(bp, ep - bp);
992 result = g_strconcat("mailto:", tmp, NULL);
998 #define ADD_TXT_POS(bp_, ep_, pti_) \
999 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1000 last = last->next; \
1001 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1002 last->next = NULL; \
1004 g_warning("alloc error scanning URIs\n"); \
1005 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1011 /* textview_make_clickable_parts() - colorizes clickable parts */
1012 static void textview_make_clickable_parts(TextView *textview,
1013 const gchar *fg_tag,
1014 const gchar *uri_tag,
1015 const gchar *linebuf)
1017 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1018 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1021 /* parse table - in order of priority */
1023 const gchar *needle; /* token */
1025 /* token search function */
1026 gchar *(*search) (const gchar *haystack,
1027 const gchar *needle);
1028 /* part parsing function */
1029 gboolean (*parse) (const gchar *start,
1030 const gchar *scanpos,
1033 /* part to URI function */
1034 gchar *(*build_uri) (const gchar *bp,
1038 static struct table parser[] = {
1039 {"http://", strcasestr, get_uri_part, make_uri_string},
1040 {"https://", strcasestr, get_uri_part, make_uri_string},
1041 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1042 {"www.", strcasestr, get_uri_part, make_uri_string},
1043 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1044 {"@", strcasestr, get_email_part, make_email_string}
1046 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1049 const gchar *walk, *bp, *ep;
1052 const gchar *bp, *ep; /* text position */
1053 gint pti; /* index in parse table */
1054 struct txtpos *next; /* next */
1055 } head = {NULL, NULL, 0, NULL}, *last = &head;
1057 gtk_text_buffer_get_end_iter(buffer, &iter);
1059 /* parse for clickable parts, and build a list of begin and end positions */
1060 for (walk = linebuf, n = 0;;) {
1061 gint last_index = PARSE_ELEMS;
1062 gchar *scanpos = NULL;
1064 /* FIXME: this looks phony. scanning for anything in the parse table */
1065 for (n = 0; n < PARSE_ELEMS; n++) {
1068 tmp = parser[n].search(walk, parser[n].needle);
1070 if (scanpos == NULL || tmp < scanpos) {
1078 /* check if URI can be parsed */
1079 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1080 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1081 ADD_TXT_POS(bp, ep, last_index);
1085 strlen(parser[last_index].needle);
1090 /* colorize this line */
1092 const gchar *normal_text = linebuf;
1095 for (last = head.next; last != NULL;
1096 normal_text = last->ep, last = last->next) {
1099 uri = g_new(RemoteURI, 1);
1100 if (last->bp - normal_text > 0)
1101 gtk_text_buffer_insert_with_tags_by_name
1104 last->bp - normal_text,
1106 uri->uri = parser[last->pti].build_uri(last->bp,
1108 uri->start = gtk_text_iter_get_offset(&iter);
1109 gtk_text_buffer_insert_with_tags_by_name
1111 last->bp, last->ep - last->bp,
1113 uri->end = gtk_text_iter_get_offset(&iter);
1114 textview->uri_list =
1115 g_slist_append(textview->uri_list, uri);
1119 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1123 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1131 static void textview_write_line(TextView *textview, const gchar *str,
1132 CodeConverter *conv)
1135 GtkTextBuffer *buffer;
1137 gchar buf[BUFFSIZE];
1139 gint quotelevel = -1;
1140 gchar quote_tag_str[10];
1142 text = GTK_TEXT_VIEW(textview->text);
1143 buffer = gtk_text_view_get_buffer(text);
1144 gtk_text_buffer_get_end_iter(buffer, &iter);
1149 if (textview->text_is_mb)
1150 conv_localetodisp(buf, sizeof(buf), str);
1152 strncpy2(buf, str, sizeof(buf));
1153 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1154 conv_localetodisp(buf, sizeof(buf), str);
1155 else if (textview->text_is_mb)
1156 conv_unreadable_locale(buf);
1159 strncpy2(buf, str, sizeof(buf));
1160 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1161 conv_localetodisp(buf, sizeof(buf), str);
1165 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1168 /* change color of quotation
1169 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1170 Up to 3 levels of quotations are detected, and each
1171 level is colored using a different color. */
1172 if (prefs_common.enable_color
1173 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1174 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1176 /* set up the correct foreground color */
1177 if (quotelevel > 2) {
1178 /* recycle colors */
1179 if (prefs_common.recycle_quote_colors)
1186 if (quotelevel == -1)
1189 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1190 "quote%d", quotelevel);
1191 fg_color = quote_tag_str;
1194 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1195 fg_color = "signature";
1196 textview->is_in_signature = TRUE;
1199 if (prefs_common.enable_color)
1200 textview_make_clickable_parts(textview, fg_color, "link", buf);
1202 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1205 void textview_write_link(TextView *textview, const gchar *str,
1206 const gchar *uri, CodeConverter *conv)
1208 GdkColor *link_color = NULL;
1210 GtkTextBuffer *buffer;
1212 gchar buf[BUFFSIZE];
1219 text = GTK_TEXT_VIEW(textview->text);
1220 buffer = gtk_text_view_get_buffer(text);
1221 gtk_text_buffer_get_end_iter(buffer, &iter);
1226 if (textview->text_is_mb)
1227 conv_localetodisp(buf, sizeof(buf), str);
1229 strncpy2(buf, str, sizeof(buf));
1230 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1231 conv_localetodisp(buf, sizeof(buf), str);
1232 else if (textview->text_is_mb)
1233 conv_unreadable_locale(buf);
1236 strncpy2(buf, str, sizeof(buf));
1237 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1238 conv_localetodisp(buf, sizeof(buf), str);
1243 gtk_text_buffer_get_end_iter(buffer, &iter);
1245 for (bufp = buf; isspace(*bufp); bufp++)
1246 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1248 if (prefs_common.enable_color) {
1249 link_color = &uri_color;
1251 r_uri = g_new(RemoteURI, 1);
1252 r_uri->uri = g_strdup(uri);
1253 r_uri->start = gtk_text_iter_get_offset(&iter);
1254 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1256 r_uri->end = gtk_text_iter_get_offset(&iter);
1257 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1260 void textview_clear(TextView *textview)
1262 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1263 GtkTextBuffer *buffer;
1265 buffer = gtk_text_view_get_buffer(text);
1266 gtk_text_buffer_set_text(buffer, "\0", -1);
1268 textview_uri_list_remove_all(textview->uri_list);
1269 textview->uri_list = NULL;
1271 textview->body_pos = 0;
1274 void textview_destroy(TextView *textview)
1276 textview_uri_list_remove_all(textview->uri_list);
1277 textview->uri_list = NULL;
1282 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1284 textview->show_all_headers = all_headers;
1287 void textview_set_font(TextView *textview, const gchar *codeset)
1289 if (prefs_common.textfont) {
1290 PangoFontDescription *font_desc = NULL;
1292 if (prefs_common.textfont)
1293 font_desc = pango_font_description_from_string
1294 (prefs_common.textfont);
1296 gtk_widget_modify_font(textview->text, font_desc);
1297 pango_font_description_free(font_desc);
1300 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1301 prefs_common.line_space / 2);
1302 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1303 prefs_common.line_space / 2);
1304 if (prefs_common.head_space) {
1305 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1307 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1311 void textview_set_text(TextView *textview, const gchar *text)
1314 GtkTextBuffer *buffer;
1316 g_return_if_fail(textview != NULL);
1317 g_return_if_fail(text != NULL);
1319 textview_clear(textview);
1321 view = GTK_TEXT_VIEW(textview->text);
1322 buffer = gtk_text_view_get_buffer(view);
1323 gtk_text_buffer_set_text(buffer, text, strlen(text));
1339 H_ORGANIZATION = 11,
1342 void textview_set_position(TextView *textview, gint pos)
1344 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1345 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1348 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1349 gtk_text_buffer_place_cursor(buffer, &iter);
1352 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1354 gchar buf[BUFFSIZE];
1355 GPtrArray *headers, *sorted_headers;
1356 GSList *disphdr_list;
1360 g_return_val_if_fail(fp != NULL, NULL);
1362 if (textview->show_all_headers)
1363 return procheader_get_header_array_asis(fp);
1365 if (!prefs_common.display_header) {
1366 while (fgets(buf, sizeof(buf), fp) != NULL)
1367 if (buf[0] == '\r' || buf[0] == '\n') break;
1371 headers = procheader_get_header_array_asis(fp);
1373 sorted_headers = g_ptr_array_new();
1375 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1376 disphdr_list = disphdr_list->next) {
1377 DisplayHeaderProp *dp =
1378 (DisplayHeaderProp *)disphdr_list->data;
1380 for (i = 0; i < headers->len; i++) {
1381 header = g_ptr_array_index(headers, i);
1383 if (procheader_headername_equal(header->name,
1386 procheader_header_free(header);
1388 g_ptr_array_add(sorted_headers, header);
1390 g_ptr_array_remove_index(headers, i);
1396 if (prefs_common.show_other_header) {
1397 for (i = 0; i < headers->len; i++) {
1398 header = g_ptr_array_index(headers, i);
1399 g_ptr_array_add(sorted_headers, header);
1401 g_ptr_array_free(headers, TRUE);
1403 procheader_header_array_destroy(headers);
1406 return sorted_headers;
1409 static void textview_show_header(TextView *textview, GPtrArray *headers)
1411 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1412 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1417 g_return_if_fail(headers != NULL);
1419 for (i = 0; i < headers->len; i++) {
1420 header = g_ptr_array_index(headers, i);
1421 g_return_if_fail(header->name != NULL);
1423 gtk_text_buffer_get_end_iter (buffer, &iter);
1424 gtk_text_buffer_insert_with_tags_by_name
1425 (buffer, &iter, header->name, -1,
1426 "header_title", "header", NULL);
1427 if (header->name[strlen(header->name) - 1] != ' ')
1430 gtk_stext_insert(text, textview->boldfont,
1431 NULL, NULL, " ", 1);
1433 gtk_text_buffer_insert_with_tags_by_name
1434 (buffer, &iter, " ", 1,
1435 "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 */
1477 GtkSText *text = GTK_STEXT(textview->text);
1481 g_return_val_if_fail(str != NULL, FALSE);
1483 len = get_mbs_len(str);
1484 g_return_val_if_fail(len >= 0, FALSE);
1486 pos = textview->cur_pos;
1487 if (pos < textview->body_pos)
1488 pos = textview->body_pos;
1490 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1491 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1492 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1493 textview_set_position(textview, pos + len);
1499 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1500 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1502 GtkTextIter iter, start, end, *pos;
1504 gint insert_offset, selbound_offset;
1506 /* reset selection */
1507 mark = gtk_text_buffer_get_mark(buffer, "insert");
1508 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1509 insert_offset = gtk_text_iter_get_offset(&start);
1510 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1511 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1512 selbound_offset = gtk_text_iter_get_offset(&end);
1514 pos = insert_offset > selbound_offset ? &start : &end;
1515 gtk_text_buffer_place_cursor(buffer, pos);
1518 mark = gtk_text_buffer_get_insert(buffer);
1519 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1520 found = gtk_text_iter_forward_search(&iter, str,
1521 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1522 &start, &end, NULL);
1524 gtk_text_buffer_place_cursor(buffer, &start);
1525 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", &end);
1526 mark = gtk_text_buffer_get_mark(buffer, "insert");
1527 gtk_text_view_scroll_mark_onscreen(text, mark);
1534 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1539 GtkSText *text = GTK_STEXT(textview->text);
1544 gboolean found = FALSE;
1546 g_return_val_if_fail(str != NULL, FALSE);
1548 wcs = strdup_mbstowcs(str);
1549 g_return_val_if_fail(wcs != NULL, FALSE);
1551 pos = textview->cur_pos;
1552 text_len = gtk_stext_get_length(text);
1553 if (text_len - textview->body_pos < len) {
1557 if (pos <= textview->body_pos || text_len - pos < len)
1558 pos = text_len - len;
1560 for (; pos >= textview->body_pos; pos--) {
1561 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1563 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1564 gtk_editable_select_region(GTK_EDITABLE(text),
1566 textview_set_position(textview, pos - 1);
1570 if (pos == textview->body_pos) break;
1576 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1577 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1579 GtkTextIter iter, start, end, *pos;
1581 gint insert_offset, selbound_offset;
1583 /* reset selection */
1584 mark = gtk_text_buffer_get_mark(buffer, "insert");
1585 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1586 insert_offset = gtk_text_iter_get_offset(&start);
1587 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1588 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1589 selbound_offset = gtk_text_iter_get_offset(&end);
1591 pos = insert_offset < selbound_offset ? &start : &end;
1592 gtk_text_buffer_place_cursor(buffer, pos);
1595 mark = gtk_text_buffer_get_insert(buffer);
1596 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1597 found = gtk_text_iter_backward_search(&iter, str,
1598 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1599 &start, &end, NULL);
1601 gtk_text_buffer_place_cursor(buffer, &end);
1602 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", &start);
1603 mark = gtk_text_buffer_get_mark(buffer, "insert");
1604 gtk_text_view_scroll_mark_onscreen(text, mark);
1611 void textview_scroll_one_line(TextView *textview, gboolean up)
1613 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1614 GtkAdjustment *vadj = text->vadjustment;
1617 if (prefs_common.enable_smooth_scroll) {
1618 textview_smooth_scroll_one_line(textview, up);
1623 upper = vadj->upper - vadj->page_size;
1624 if (vadj->value < upper) {
1626 vadj->step_increment * 4;
1628 MIN(vadj->value, upper);
1629 g_signal_emit_by_name(G_OBJECT(vadj),
1630 "value_changed", 0);
1633 if (vadj->value > 0.0) {
1635 vadj->step_increment * 4;
1637 MAX(vadj->value, 0.0);
1638 g_signal_emit_by_name(G_OBJECT(vadj),
1639 "value_changed", 0);
1644 gboolean textview_scroll_page(TextView *textview, gboolean up)
1646 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1647 GtkAdjustment *vadj = text->vadjustment;
1651 if (prefs_common.enable_smooth_scroll)
1652 return textview_smooth_scroll_page(textview, up);
1654 if (prefs_common.scroll_halfpage)
1655 page_incr = vadj->page_increment / 2;
1657 page_incr = vadj->page_increment;
1660 upper = vadj->upper - vadj->page_size;
1661 if (vadj->value < upper) {
1662 vadj->value += page_incr;
1663 vadj->value = MIN(vadj->value, upper);
1664 g_signal_emit_by_name(G_OBJECT(vadj),
1665 "value_changed", 0);
1669 if (vadj->value > 0.0) {
1670 vadj->value -= page_incr;
1671 vadj->value = MAX(vadj->value, 0.0);
1672 g_signal_emit_by_name(G_OBJECT(vadj),
1673 "value_changed", 0);
1681 static void textview_smooth_scroll_do(TextView *textview,
1682 gfloat old_value, gfloat last_value,
1685 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1686 GtkAdjustment *vadj = text->vadjustment;
1691 if (old_value < last_value) {
1692 change_value = last_value - old_value;
1695 change_value = old_value - last_value;
1700 /* gdk_key_repeat_disable(); */
1702 for (i = step; i <= change_value; i += step) {
1703 vadj->value = old_value + (up ? -i : i);
1704 g_signal_emit_by_name(G_OBJECT(vadj),
1705 "value_changed", 0);
1708 vadj->value = last_value;
1709 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1712 /* gdk_key_repeat_restore(); */
1715 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1717 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1718 GtkAdjustment *vadj = text->vadjustment;
1724 upper = vadj->upper - vadj->page_size;
1725 if (vadj->value < upper) {
1726 old_value = vadj->value;
1727 last_value = vadj->value +
1728 vadj->step_increment * 4;
1729 last_value = MIN(last_value, upper);
1731 textview_smooth_scroll_do(textview, old_value,
1733 prefs_common.scroll_step);
1736 if (vadj->value > 0.0) {
1737 old_value = vadj->value;
1738 last_value = vadj->value -
1739 vadj->step_increment * 4;
1740 last_value = MAX(last_value, 0.0);
1742 textview_smooth_scroll_do(textview, old_value,
1744 prefs_common.scroll_step);
1749 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1751 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1752 GtkAdjustment *vadj = text->vadjustment;
1758 if (prefs_common.scroll_halfpage)
1759 page_incr = vadj->page_increment / 2;
1761 page_incr = vadj->page_increment;
1764 upper = vadj->upper - vadj->page_size;
1765 if (vadj->value < upper) {
1766 old_value = vadj->value;
1767 last_value = vadj->value + page_incr;
1768 last_value = MIN(last_value, upper);
1770 textview_smooth_scroll_do(textview, old_value,
1772 prefs_common.scroll_step);
1776 if (vadj->value > 0.0) {
1777 old_value = vadj->value;
1778 last_value = vadj->value - page_incr;
1779 last_value = MAX(last_value, 0.0);
1781 textview_smooth_scroll_do(textview, old_value,
1783 prefs_common.scroll_step);
1793 #define KEY_PRESS_EVENT_STOP() \
1794 if (gtk_signal_n_emissions_by_name \
1795 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1796 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1797 "key_press_event"); \
1800 #define KEY_PRESS_EVENT_STOP() \
1801 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1805 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1808 SummaryView *summaryview = NULL;
1809 MessageView *messageview = textview->messageview;
1811 if (!event) return FALSE;
1812 if (messageview->mainwin)
1813 summaryview = messageview->mainwin->summaryview;
1815 switch (event->keyval) {
1830 summary_pass_key_press_event(summaryview, event);
1832 textview_scroll_page(textview, FALSE);
1835 textview_scroll_page(textview, TRUE);
1838 textview_scroll_one_line(textview,
1839 (event->state & GDK_MOD1_MASK) != 0);
1843 summary_pass_key_press_event(summaryview, event);
1848 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1849 KEY_PRESS_EVENT_STOP();
1850 mimeview_pass_key_press_event(messageview->mimeview,
1854 /* possible fall through */
1857 event->window != messageview->mainwin->window->window) {
1858 GdkEventKey tmpev = *event;
1860 tmpev.window = messageview->mainwin->window->window;
1861 KEY_PRESS_EVENT_STOP();
1862 gtk_widget_event(messageview->mainwin->window,
1863 (GdkEvent *)&tmpev);
1871 static gint show_url_timeout_cb(gpointer data)
1873 TextView *textview = (TextView *)data;
1875 if (textview->messageview->mainwin)
1876 if (textview->show_url_msgid)
1877 gtk_statusbar_remove(GTK_STATUSBAR(
1878 textview->messageview->mainwin->statusbar),
1879 textview->messageview->mainwin->folderview_cid,
1880 textview->show_url_msgid);
1885 *\brief Check to see if a web URL has been disguised as a different
1886 * URL (possible with HTML email).
1888 *\param uri The uri to check
1890 *\param textview The TextView the URL is contained in
1892 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1893 * it anyway, otherwise FALSE
1895 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1898 gboolean retval = TRUE;
1900 if (g_strncasecmp(uri->uri, "http:", 5) &&
1901 g_strncasecmp(uri->uri, "https:", 6) &&
1902 g_strncasecmp(uri->uri, "www.", 4))
1905 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1908 if (clicked_str == NULL)
1911 if (strcmp(clicked_str, uri->uri) &&
1912 (!g_strncasecmp(clicked_str, "http:", 5) ||
1913 !g_strncasecmp(clicked_str, "https:", 6) ||
1914 !g_strncasecmp(clicked_str, "www.", 4))) {
1918 /* allow uri->uri == http://somewhere.com
1919 and clicked_str == somewhere.com */
1920 str = g_strconcat("http://", clicked_str, NULL);
1922 if (!g_strcasecmp(str, uri->uri))
1927 if (retval == FALSE) {
1931 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1932 "the apparent URL (%s). \n"
1934 uri->uri, clicked_str);
1935 resp = alertpanel(_("Warning"),
1941 if (resp == G_ALERTDEFAULT)
1944 g_free(clicked_str);
1950 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1954 textview->last_buttonpress = event->type;
1958 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1962 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1965 ((event->button == 1)
1966 || event->button == 2 || event->button == 3)) {
1969 /* double click seems to set the cursor after the current
1970 * word. The cursor position needs fixing, otherwise the
1971 * last word of a clickable zone will not work */
1972 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1973 textview->cur_pos--;
1976 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1977 RemoteURI *uri = (RemoteURI *)cur->data;
1979 if (textview->cur_pos >= uri->start &&
1980 textview->cur_pos <= uri->end) {
1983 trimmed_uri = trim_string(uri->uri, 60);
1984 /* single click: display url in statusbar */
1985 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1986 if (textview->messageview->mainwin) {
1987 if (textview->show_url_msgid) {
1988 gtk_timeout_remove(textview->show_url_timeout_tag);
1989 gtk_statusbar_remove(GTK_STATUSBAR(
1990 textview->messageview->mainwin->statusbar),
1991 textview->messageview->mainwin->folderview_cid,
1992 textview->show_url_msgid);
1993 textview->show_url_msgid = 0;
1995 textview->show_url_msgid = gtk_statusbar_push(
1996 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1997 textview->messageview->mainwin->folderview_cid,
1999 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
2000 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
2003 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
2004 if (event->button == 3) {
2005 gchar *fromname, *fromaddress;
2008 fromaddress = g_strdup(uri->uri + 7);
2009 /* Hiroyuki: please put this function in utils.c! */
2010 fromname = procheader_get_fromname(fromaddress);
2011 extract_address(fromaddress);
2012 g_message("adding from textview %s <%s>", fromname, fromaddress);
2013 /* Add to address book - Match */
2014 addressbook_add_contact( fromname, fromaddress, NULL );
2016 g_free(fromaddress);
2019 PrefsAccount *account = NULL;
2020 FolderItem *folder_item;
2022 if (textview->messageview && textview->messageview->mainwin
2023 && textview->messageview->mainwin->summaryview
2024 && textview->messageview->mainwin->summaryview->folder_item) {
2025 folder_item = textview->messageview->mainwin->summaryview->folder_item;
2026 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2027 account = account_find_from_id(folder_item->prefs->default_account);
2029 compose_new(account, uri->uri + 7, NULL);
2032 if (uri_security_check(uri, textview) == TRUE)
2034 prefs_common.uri_cmd);
2036 g_free(trimmed_uri);
2041 textview->last_buttonpress = event->type;
2045 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2046 GdkEvent *event, GtkTextIter *iter,
2049 GtkTextIter start_iter, end_iter;
2050 gint start_pos, end_pos;
2051 GdkEventButton *bevent;
2053 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS)
2056 bevent = (GdkEventButton *) event;
2059 ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) ||
2060 bevent->button == 2)) {
2065 if(!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) {
2066 debug_print("Can't find start.");
2069 start_pos = gtk_text_iter_get_offset(&start_iter);
2072 if(!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) {
2073 debug_print("Can't find end");
2076 end_pos = gtk_text_iter_get_offset(&end_iter);
2078 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2079 RemoteURI *uri = (RemoteURI *)cur->data;
2081 if (start_pos == uri->start &&
2082 end_pos == uri->end) {
2083 if (!g_strncasecmp(uri->uri, "mailto:", 7))
2084 compose_new(NULL, uri->uri + 7, NULL);
2087 prefs_common.uri_cmd);
2097 static void textview_uri_list_remove_all(GSList *uri_list)
2101 for (cur = uri_list; cur != NULL; cur = cur->next) {
2103 g_free(((RemoteURI *)cur->data)->uri);
2108 g_slist_free(uri_list);