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"
50 #include "addressbook.h"
51 #include "displayheader.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 static GdkColor good_sig_color = {
109 static GdkColor nocheck_sig_color = {
116 static GdkColor bad_sig_color = {
124 static GdkFont *text_sb_font;
125 static GdkFont *text_mb_font;
126 static gint text_sb_font_orig_ascent;
127 static gint text_sb_font_orig_descent;
128 static gint text_mb_font_orig_ascent;
129 static gint text_mb_font_orig_descent;
130 static GdkFont *spacingfont;
132 static void textview_show_ertf (TextView *textview,
134 CodeConverter *conv);
135 static void textview_add_part (TextView *textview,
138 static void textview_add_parts (TextView *textview,
141 static void textview_write_body (TextView *textview,
144 const gchar *charset);
145 static void textview_show_html (TextView *textview,
147 CodeConverter *conv);
148 static void textview_write_line (TextView *textview,
150 CodeConverter *conv);
151 static void textview_write_link (TextView *textview,
154 CodeConverter *conv);
155 static GPtrArray *textview_scan_header (TextView *textview,
157 static void textview_show_header (TextView *textview,
160 static gint textview_key_pressed (GtkWidget *widget,
163 static gint textview_button_pressed (GtkWidget *widget,
164 GdkEventButton *event,
166 static gint textview_button_released (GtkWidget *widget,
167 GdkEventButton *event,
170 static void textview_uri_list_remove_all(GSList *uri_list);
172 static void textview_smooth_scroll_do (TextView *textview,
176 static void textview_smooth_scroll_one_line (TextView *textview,
178 static gboolean textview_smooth_scroll_page (TextView *textview,
182 TextView *textview_create(void)
186 GtkWidget *scrolledwin_sb;
187 GtkWidget *scrolledwin_mb;
191 debug_print("Creating text view...\n");
192 textview = g_new0(TextView, 1);
194 scrolledwin_sb = gtk_scrolled_window_new(NULL, NULL);
195 scrolledwin_mb = gtk_scrolled_window_new(NULL, NULL);
196 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_sb),
197 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
198 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_mb),
199 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
200 gtk_widget_set_usize(scrolledwin_sb, prefs_common.mainview_width, -1);
201 gtk_widget_set_usize(scrolledwin_mb, prefs_common.mainview_width, -1);
203 /* create GtkSText widgets for single-byte and multi-byte character */
204 text_sb = gtk_stext_new(NULL, NULL);
205 text_mb = gtk_stext_new(NULL, NULL);
206 GTK_STEXT(text_sb)->default_tab_width = 8;
207 GTK_STEXT(text_mb)->default_tab_width = 8;
208 gtk_widget_show(text_sb);
209 gtk_widget_show(text_mb);
210 gtk_stext_set_word_wrap(GTK_STEXT(text_sb), TRUE);
211 gtk_stext_set_word_wrap(GTK_STEXT(text_mb), TRUE);
212 gtk_widget_ensure_style(text_sb);
213 gtk_widget_ensure_style(text_mb);
214 if (text_sb->style && text_sb->style->font->type == GDK_FONT_FONTSET) {
218 font = gtkut_font_load_from_fontset(prefs_common.normalfont);
220 style = gtk_style_copy(text_sb->style);
221 gdk_font_unref(style->font);
223 gtk_widget_set_style(text_sb, style);
226 if (text_mb->style && text_mb->style->font->type == GDK_FONT_FONT) {
230 font = gdk_fontset_load(prefs_common.normalfont);
232 style = gtk_style_copy(text_mb->style);
233 gdk_font_unref(style->font);
235 gtk_widget_set_style(text_mb, style);
238 gtk_widget_ref(scrolledwin_sb);
239 gtk_widget_ref(scrolledwin_mb);
241 gtk_container_add(GTK_CONTAINER(scrolledwin_sb), text_sb);
242 gtk_container_add(GTK_CONTAINER(scrolledwin_mb), text_mb);
243 gtk_signal_connect(GTK_OBJECT(text_sb), "key_press_event",
244 GTK_SIGNAL_FUNC(textview_key_pressed),
246 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_press_event",
247 GTK_SIGNAL_FUNC(textview_button_pressed),
249 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_release_event",
250 GTK_SIGNAL_FUNC(textview_button_released),
252 gtk_signal_connect(GTK_OBJECT(text_mb), "key_press_event",
253 GTK_SIGNAL_FUNC(textview_key_pressed),
255 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_press_event",
256 GTK_SIGNAL_FUNC(textview_button_pressed),
258 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_release_event",
259 GTK_SIGNAL_FUNC(textview_button_released),
262 gtk_widget_show(scrolledwin_sb);
263 gtk_widget_show(scrolledwin_mb);
265 vbox = gtk_vbox_new(FALSE, 0);
266 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin_sb, TRUE, TRUE, 0);
268 gtk_widget_show(vbox);
270 textview->vbox = vbox;
271 textview->scrolledwin = scrolledwin_sb;
272 textview->scrolledwin_sb = scrolledwin_sb;
273 textview->scrolledwin_mb = scrolledwin_mb;
274 textview->text = text_sb;
275 textview->text_sb = text_sb;
276 textview->text_mb = text_mb;
277 textview->text_is_mb = FALSE;
278 textview->uri_list = NULL;
279 textview->body_pos = 0;
280 textview->cur_pos = 0;
281 textview->show_all_headers = FALSE;
282 textview->last_buttonpress = GDK_NOTHING;
283 textview->show_url_msgid = 0;
288 void textview_init(TextView *textview)
290 gtkut_widget_disable_theme_engine(textview->text_sb);
291 gtkut_widget_disable_theme_engine(textview->text_mb);
292 textview_update_message_colors();
293 textview_set_all_headers(textview, FALSE);
294 textview_set_font(textview, NULL);
297 void textview_update_message_colors(void)
299 GdkColor black = {0, 0, 0, 0};
301 if (prefs_common.enable_color) {
302 /* grab the quote colors, converting from an int to a GdkColor */
303 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
305 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
307 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
309 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
311 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
314 quote_colors[0] = quote_colors[1] = quote_colors[2] =
315 uri_color = emphasis_color = signature_color = black;
319 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
324 const gchar *charset = NULL;
325 GPtrArray *headers = NULL;
327 if ((fp = fopen(file, "rb")) == NULL) {
328 FILE_OP_ERROR(file, "fopen");
332 if (prefs_common.force_charset)
333 charset = prefs_common.force_charset;
334 else if (mimeinfo->charset)
335 charset = mimeinfo->charset;
336 textview_set_font(textview, charset);
337 textview_clear(textview);
339 text = GTK_STEXT(textview->text);
341 gtk_stext_freeze(text);
343 if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) perror("fseek");
344 headers = textview_scan_header(textview, fp);
346 textview_show_header(textview, headers);
347 procheader_header_array_destroy(headers);
348 textview->body_pos = gtk_stext_get_length(text);
351 textview_add_parts(textview, mimeinfo, fp);
353 gtk_stext_thaw(text);
358 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
362 const gchar *boundary = NULL;
363 gint boundary_len = 0;
364 const gchar *charset = NULL;
365 GPtrArray *headers = NULL;
366 gboolean is_rfc822_part = FALSE;
368 g_return_if_fail(mimeinfo != NULL);
369 g_return_if_fail(fp != NULL);
371 if (mimeinfo->mime_type == MIME_MULTIPART) {
372 textview_clear(textview);
373 textview_add_parts(textview, mimeinfo, fp);
377 if (mimeinfo->parent && mimeinfo->parent->boundary) {
378 boundary = mimeinfo->parent->boundary;
379 boundary_len = strlen(boundary);
382 if (!boundary && (mimeinfo->mime_type == MIME_TEXT ||
383 mimeinfo->mime_type == MIME_TEXT_HTML ||
384 mimeinfo->mime_type == MIME_TEXT_ENRICHED)) {
386 if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0)
388 headers = textview_scan_header(textview, fp);
390 if (mimeinfo->mime_type == MIME_TEXT && mimeinfo->parent) {
392 MimeInfo *parent = mimeinfo->parent;
394 while (parent->parent) {
396 parent->main->mime_type ==
399 parent = parent->parent;
402 if ((fpos = ftell(fp)) < 0)
404 else if (fseek(fp, parent->fpos, SEEK_SET) < 0)
407 headers = textview_scan_header(textview, fp);
408 if (fseek(fp, fpos, SEEK_SET) < 0)
412 /* skip MIME part headers */
413 while (fgets(buf, sizeof(buf), fp) != NULL)
414 if (buf[0] == '\r' || buf[0] == '\n') break;
417 /* display attached RFC822 single text message */
418 if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
419 if (headers) procheader_header_array_destroy(headers);
420 if (!mimeinfo->sub) {
421 textview_clear(textview);
424 headers = textview_scan_header(textview, fp);
425 mimeinfo = mimeinfo->sub;
426 is_rfc822_part = TRUE;
429 if (prefs_common.force_charset)
430 charset = prefs_common.force_charset;
431 else if (mimeinfo->charset)
432 charset = mimeinfo->charset;
433 textview_set_font(textview, charset);
435 text = GTK_STEXT(textview->text);
437 gtk_stext_freeze(text);
438 textview_clear(textview);
441 textview_show_header(textview, headers);
442 procheader_header_array_destroy(headers);
443 textview->body_pos = gtk_stext_get_length(text);
445 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
448 if (mimeinfo->mime_type == MIME_MULTIPART || is_rfc822_part)
449 textview_add_parts(textview, mimeinfo, fp);
451 textview_write_body(textview, mimeinfo, fp, charset);
453 gtk_stext_thaw(text);
456 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
458 GtkSText *text = GTK_STEXT(textview->text);
460 const gchar *boundary = NULL;
461 gint boundary_len = 0;
462 const gchar *charset = NULL;
463 GPtrArray *headers = NULL;
465 g_return_if_fail(mimeinfo != NULL);
466 g_return_if_fail(fp != NULL);
468 if (mimeinfo->mime_type == MIME_MULTIPART) return;
470 if (!mimeinfo->parent &&
471 mimeinfo->mime_type != MIME_TEXT &&
472 mimeinfo->mime_type != MIME_TEXT_HTML &&
473 mimeinfo->mime_type != MIME_TEXT_ENRICHED)
476 if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) {
481 if (mimeinfo->parent && mimeinfo->parent->boundary) {
482 boundary = mimeinfo->parent->boundary;
483 boundary_len = strlen(boundary);
486 while (fgets(buf, sizeof(buf), fp) != NULL)
487 if (buf[0] == '\r' || buf[0] == '\n') break;
489 if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
490 headers = textview_scan_header(textview, fp);
492 gtk_stext_freeze(text);
493 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
494 textview_show_header(textview, headers);
495 procheader_header_array_destroy(headers);
496 gtk_stext_thaw(text);
501 gtk_stext_freeze(text);
504 if (mimeinfo->sigstatus)
505 g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n",
506 mimeinfo->content_type, mimeinfo->sigstatus);
509 if (mimeinfo->filename || mimeinfo->name)
510 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
511 mimeinfo->filename ? mimeinfo->filename :
513 mimeinfo->content_type, mimeinfo->size);
515 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
516 mimeinfo->content_type, mimeinfo->size);
519 if (mimeinfo->sigstatus && !mimeinfo->sigstatus_full) {
521 /* use standard font */
522 gpointer oldfont = textview->msgfont;
523 textview->msgfont = NULL;
525 tmp = g_strconcat("pgp: ", _("Check signature"), NULL);
526 textview_write_link(textview, buf, tmp, NULL);
528 /* put things back */
529 textview->msgfont = (GdkFont *)oldfont;
532 } else if (mimeinfo->sigstatus) {
534 if (!strcmp(mimeinfo->sigstatus, _("Good signature")))
535 color = &good_sig_color;
536 else if (!strcmp(mimeinfo->sigstatus, _("BAD signature")))
537 color = &bad_sig_color;
539 color = &nocheck_sig_color;
540 gtk_stext_insert(text, NULL, color, NULL, buf, -1);
543 if (mimeinfo->mime_type != MIME_TEXT &&
544 mimeinfo->mime_type != MIME_TEXT_HTML &&
545 mimeinfo->mime_type != MIME_TEXT_ENRICHED) {
546 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
548 if (!mimeinfo->main &&
550 mimeinfo->parent->children != mimeinfo)
551 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
552 else if (prefs_common.display_header)
553 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
554 if (prefs_common.force_charset)
555 charset = prefs_common.force_charset;
556 else if (mimeinfo->charset)
557 charset = mimeinfo->charset;
558 textview_write_body(textview, mimeinfo, fp, charset);
561 gtk_stext_thaw(text);
564 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
568 g_return_if_fail(mimeinfo != NULL);
569 g_return_if_fail(fp != NULL);
571 level = mimeinfo->level;
574 textview_add_part(textview, mimeinfo, fp);
575 if (mimeinfo->parent && mimeinfo->parent->content_type &&
576 !strcasecmp(mimeinfo->parent->content_type,
577 "multipart/alternative"))
578 mimeinfo = mimeinfo->parent->next;
580 mimeinfo = procmime_mimeinfo_next(mimeinfo);
581 if (!mimeinfo || mimeinfo->level <= level)
586 #define TEXT_INSERT(str) \
587 gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
589 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
593 if (!partinfo) return;
595 textview_set_font(textview, NULL);
596 text = GTK_STEXT(textview->text);
597 textview_clear(textview);
599 gtk_stext_freeze(text);
601 TEXT_INSERT(_("To save this part, pop up the context menu with "));
602 TEXT_INSERT(_("right click and select `Save as...', "));
603 TEXT_INSERT(_("or press `y' key.\n\n"));
605 TEXT_INSERT(_("To display this part as a text message, select "));
606 TEXT_INSERT(_("`Display as text', or press `t' key.\n\n"));
608 TEXT_INSERT(_("To display this part as an image, select "));
609 TEXT_INSERT(_("`Display image', or press `i' key.\n\n"));
611 TEXT_INSERT(_("To open this part with external program, select "));
612 TEXT_INSERT(_("`Open' or `Open with...', "));
613 TEXT_INSERT(_("or double-click, or click the center button, "));
614 TEXT_INSERT(_("or press `l' key."));
616 gtk_stext_thaw(text);
620 void textview_show_signature_part(TextView *textview, MimeInfo *partinfo)
624 if (!partinfo) return;
626 textview_set_font(textview, NULL);
627 text = GTK_STEXT(textview->text);
628 textview_clear(textview);
630 gtk_stext_freeze(text);
632 if (partinfo->sigstatus_full == NULL) {
633 TEXT_INSERT(_("This signature has not been checked yet.\n"));
634 TEXT_INSERT(_("To check it, pop up the context menu with\n"));
635 TEXT_INSERT(_("right click and select `Check signature'.\n"));
637 TEXT_INSERT(partinfo->sigstatus_full);
640 gtk_stext_thaw(text);
642 #endif /* USE_GPGME */
646 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
647 FILE *fp, const gchar *charset)
653 conv = conv_code_converter_new(charset);
655 tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
657 textview->is_in_signature = FALSE;
660 if (mimeinfo->mime_type == MIME_TEXT_HTML)
661 textview_show_html(textview, tmpfp, conv);
662 else if (mimeinfo->mime_type == MIME_TEXT_ENRICHED)
663 textview_show_ertf(textview, tmpfp, conv);
665 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
666 textview_write_line(textview, buf, conv);
670 conv_code_converter_destroy(conv);
673 static void textview_show_html(TextView *textview, FILE *fp,
679 parser = html_parser_new(fp, conv);
680 g_return_if_fail(parser != NULL);
682 while ((str = html_parse(parser)) != NULL) {
683 if (parser->state == HTML_HREF) {
684 /* first time : get and copy the URL */
685 if (parser->href == NULL) {
686 /* ALF - the sylpheed html parser returns an empty string,
687 * if still inside an <a>, but already parsed past HREF */
688 str = strtok(str, " ");
690 parser->href = strdup(str);
691 /* the URL may (or not) be followed by the
693 str = strtok(NULL, "");
697 textview_write_link(textview, str, parser->href, NULL);
699 textview_write_line(textview, str, NULL);
701 html_parser_destroy(parser);
704 static void textview_show_ertf(TextView *textview, FILE *fp,
710 parser = ertf_parser_new(fp, conv);
711 g_return_if_fail(parser != NULL);
713 while ((str = ertf_parse(parser)) != NULL) {
714 textview_write_line(textview, str, NULL);
717 ertf_parser_destroy(parser);
720 /* get_uri_part() - retrieves a URI starting from scanpos.
721 Returns TRUE if succesful */
722 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
723 const gchar **bp, const gchar **ep)
727 g_return_val_if_fail(start != NULL, FALSE);
728 g_return_val_if_fail(scanpos != NULL, FALSE);
729 g_return_val_if_fail(bp != NULL, FALSE);
730 g_return_val_if_fail(ep != NULL, FALSE);
734 /* find end point of URI */
735 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
736 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
740 /* no punctuation at end of string */
742 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
743 * should pass some URI type to this function and decide on that whether
744 * to perform punctuation stripping */
746 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
748 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
758 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
760 return g_strndup(bp, ep - bp);
763 /* valid mail address characters */
764 #define IS_RFC822_CHAR(ch) \
769 !strchr("(),;<>\"", (ch)))
771 /* alphabet and number within 7bit ASCII */
772 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
773 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
775 static GHashTable *create_domain_tab(void)
777 static const gchar *toplvl_domains [] = {
779 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
780 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
781 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
782 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
783 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
784 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
785 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
786 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
787 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
788 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
789 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
790 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
791 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
792 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
793 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
794 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
795 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
796 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
797 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
798 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
799 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
800 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
801 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
802 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
803 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
804 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
807 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
809 g_return_val_if_fail(htab, NULL);
810 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
811 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
815 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
817 const gint MAX_LVL_DOM_NAME_LEN = 6;
818 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
819 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
822 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
825 for (p = buf; p < m && first < last; *p++ = *first++)
829 return g_hash_table_lookup(tab, buf) != NULL;
832 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
833 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
834 const gchar **bp, const gchar **ep)
836 /* more complex than the uri part because we need to scan back and forward starting from
837 * the scan position. */
838 gboolean result = FALSE;
839 const gchar *bp_ = NULL;
840 const gchar *ep_ = NULL;
841 static GHashTable *dom_tab;
842 const gchar *last_dot = NULL;
843 const gchar *prelast_dot = NULL;
844 const gchar *last_tld_char = NULL;
846 /* the informative part of the email address (describing the name
847 * of the email address owner) may contain quoted parts. the
848 * closure stack stores the last encountered quotes. */
849 gchar closure_stack[128];
850 gchar *ptr = closure_stack;
852 g_return_val_if_fail(start != NULL, FALSE);
853 g_return_val_if_fail(scanpos != NULL, FALSE);
854 g_return_val_if_fail(bp != NULL, FALSE);
855 g_return_val_if_fail(ep != NULL, FALSE);
858 dom_tab = create_domain_tab();
859 g_return_val_if_fail(dom_tab, FALSE);
861 /* scan start of address */
862 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
865 /* TODO: should start with an alnum? */
867 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
870 if (bp_ != scanpos) {
871 /* scan end of address */
872 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
874 prelast_dot = last_dot;
876 if (*(last_dot + 1) == '.') {
877 if (prelast_dot == NULL)
879 last_dot = prelast_dot;
884 /* TODO: really should terminate with an alnum? */
885 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
889 if (last_dot == NULL)
892 last_dot = prelast_dot;
893 if (last_dot == NULL || (scanpos + 1 >= last_dot))
897 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
898 if (*last_tld_char == '?')
901 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
908 if (!result) return FALSE;
910 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
911 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
914 /* see if this is <bracketed>; in this case we also scan for the informative part. */
915 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
918 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
919 #define IN_STACK() (ptr > closure_stack)
920 /* has underrun check */
921 #define POP_STACK() if(IN_STACK()) --ptr
922 /* has overrun check */
923 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
924 /* has underrun check */
925 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
929 /* scan for the informative part. */
930 for (bp_ -= 2; bp_ >= start; bp_--) {
931 /* if closure on the stack keep scanning */
932 if (PEEK_STACK() == *bp_) {
936 if (*bp_ == '\'' || *bp_ == '"') {
941 /* if nothing in the closure stack, do the special conditions
942 * the following if..else expression simply checks whether
943 * a token is acceptable. if not acceptable, the clause
944 * should terminate the loop with a 'break' */
947 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
948 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
949 /* hyphens are allowed, but only in
951 } else if (!ispunct(*bp_)) {
952 /* but anything not being a punctiation
955 break; /* anything else is rejected */
968 /* scan forward (should start with an alnum) */
969 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
979 #undef IS_RFC822_CHAR
981 static gchar *make_email_string(const gchar *bp, const gchar *ep)
983 /* returns a mailto: URI; mailto: is also used to detect the
984 * uri type later on in the button_pressed signal handler */
988 tmp = g_strndup(bp, ep - bp);
989 result = g_strconcat("mailto:", tmp, NULL);
995 #define ADD_TXT_POS(bp_, ep_, pti_) \
996 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
998 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1001 g_warning("alloc error scanning URIs\n"); \
1002 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
1007 /* textview_make_clickable_parts() - colorizes clickable parts */
1008 static void textview_make_clickable_parts(TextView *textview,
1011 GdkColor *uri_color,
1012 const gchar *linebuf)
1014 /* parse table - in order of priority */
1016 const gchar *needle; /* token */
1018 /* token search function */
1019 gchar *(*search) (const gchar *haystack,
1020 const gchar *needle);
1021 /* part parsing function */
1022 gboolean (*parse) (const gchar *start,
1023 const gchar *scanpos,
1026 /* part to URI function */
1027 gchar *(*build_uri) (const gchar *bp,
1031 static struct table parser[] = {
1032 {"http://", strcasestr, get_uri_part, make_uri_string},
1033 {"https://", strcasestr, get_uri_part, make_uri_string},
1034 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1035 {"www.", strcasestr, get_uri_part, make_uri_string},
1036 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1037 {"@", strcasestr, get_email_part, make_email_string}
1039 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1042 const gchar *walk, *bp, *ep;
1045 const gchar *bp, *ep; /* text position */
1046 gint pti; /* index in parse table */
1047 struct txtpos *next; /* next */
1048 } head = {NULL, NULL, 0, NULL}, *last = &head;
1050 GtkSText *text = GTK_STEXT(textview->text);
1052 /* parse for clickable parts, and build a list of begin and end positions */
1053 for (walk = linebuf, n = 0;;) {
1054 gint last_index = PARSE_ELEMS;
1055 gchar *scanpos = NULL;
1057 /* FIXME: this looks phony. scanning for anything in the parse table */
1058 for (n = 0; n < PARSE_ELEMS; n++) {
1061 tmp = parser[n].search(walk, parser[n].needle);
1063 if (scanpos == NULL || tmp < scanpos) {
1071 /* check if URI can be parsed */
1072 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1073 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1074 ADD_TXT_POS(bp, ep, last_index);
1078 strlen(parser[last_index].needle);
1083 /* colorize this line */
1085 const gchar *normal_text = linebuf;
1088 for (last = head.next; last != NULL;
1089 normal_text = last->ep, last = last->next) {
1092 uri = g_new(RemoteURI, 1);
1093 if (last->bp - normal_text > 0)
1094 gtk_stext_insert(text, font,
1097 last->bp - normal_text);
1098 uri->uri = parser[last->pti].build_uri(last->bp,
1100 uri->start = gtk_stext_get_point(text);
1101 gtk_stext_insert(text, font, uri_color,
1102 NULL, last->bp, last->ep - last->bp);
1103 uri->end = gtk_stext_get_point(text);
1104 textview->uri_list =
1105 g_slist_append(textview->uri_list, uri);
1109 gtk_stext_insert(text, font, fg_color,
1110 NULL, normal_text, -1);
1112 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1117 static void textview_write_line(TextView *textview, const gchar *str,
1118 CodeConverter *conv)
1120 GtkSText *text = GTK_STEXT(textview->text);
1121 gchar buf[BUFFSIZE];
1123 gint quotelevel = -1;
1126 if (textview->text_is_mb)
1127 conv_localetodisp(buf, sizeof(buf), str);
1129 strncpy2(buf, str, sizeof(buf));
1130 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1131 conv_localetodisp(buf, sizeof(buf), str);
1132 else if (textview->text_is_mb)
1133 conv_unreadable_locale(buf);
1136 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1139 /* change color of quotation
1140 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1141 Up to 3 levels of quotations are detected, and each
1142 level is colored using a different color. */
1143 if (prefs_common.enable_color
1144 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1145 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1147 /* set up the correct foreground color */
1148 if (quotelevel > 2) {
1149 /* recycle colors */
1150 if (prefs_common.recycle_quote_colors)
1157 if (quotelevel == -1)
1160 fg_color = "e_colors[quotelevel];
1162 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1163 fg_color = &signature_color;
1164 textview->is_in_signature = TRUE;
1167 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1168 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1170 if (prefs_common.enable_color)
1171 textview_make_clickable_parts(textview, textview->msgfont,
1172 fg_color, &uri_color, buf);
1174 textview_make_clickable_parts(textview, textview->msgfont,
1175 fg_color, NULL, buf);
1178 void textview_write_link(TextView *textview, const gchar *str,
1179 const gchar *uri, CodeConverter *conv)
1181 GdkColor *link_color = NULL;
1182 GtkSText *text = GTK_STEXT(textview->text);
1183 gchar buf[BUFFSIZE];
1191 if (textview->text_is_mb)
1192 conv_localetodisp(buf, sizeof(buf), str);
1194 strncpy2(buf, str, sizeof(buf));
1195 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1196 conv_localetodisp(buf, sizeof(buf), str);
1197 else if (textview->text_is_mb)
1198 conv_unreadable_locale(buf);
1202 for (bufp = buf; isspace(*bufp); bufp++)
1203 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1205 if (prefs_common.enable_color) {
1206 link_color = &uri_color;
1208 r_uri = g_new(RemoteURI, 1);
1209 r_uri->uri = g_strdup(uri);
1210 r_uri->start = gtk_stext_get_point(text);
1211 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1212 r_uri->end = gtk_stext_get_point(text);
1213 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1216 void textview_clear(TextView *textview)
1218 GtkSText *text = GTK_STEXT(textview->text);
1220 gtk_stext_freeze(text);
1221 gtk_stext_set_point(text, 0);
1222 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1223 gtk_stext_thaw(text);
1225 textview_uri_list_remove_all(textview->uri_list);
1226 textview->uri_list = NULL;
1228 textview->body_pos = 0;
1229 textview->cur_pos = 0;
1232 void textview_destroy(TextView *textview)
1234 textview_uri_list_remove_all(textview->uri_list);
1235 textview->uri_list = NULL;
1237 if (!textview->scrolledwin_sb->parent)
1238 gtk_widget_destroy(textview->scrolledwin_sb);
1239 if (!textview->scrolledwin_mb->parent)
1240 gtk_widget_destroy(textview->scrolledwin_mb);
1242 if (textview->msgfont)
1243 gdk_font_unref(textview->msgfont);
1244 if (textview->boldfont)
1245 gdk_font_unref(textview->boldfont);
1250 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1252 textview->show_all_headers = all_headers;
1255 void textview_set_font(TextView *textview, const gchar *codeset)
1257 gboolean use_fontset = TRUE;
1259 /* In multi-byte mode, GtkSText can't display 8bit characters
1260 correctly, so it must be single-byte mode. */
1261 if (MB_CUR_MAX > 1) {
1262 if (codeset && conv_get_current_charset() != C_UTF_8) {
1263 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1264 !g_strcasecmp(codeset, "BALTIC"))
1265 use_fontset = FALSE;
1266 else if (conv_get_current_charset() != C_EUC_JP &&
1267 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1268 !g_strncasecmp(codeset, "CP", 2) ||
1269 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1270 use_fontset = FALSE;
1273 use_fontset = FALSE;
1275 if (textview->text_is_mb && !use_fontset) {
1278 parent = textview->scrolledwin_mb->parent;
1279 gtkut_container_remove(GTK_CONTAINER(parent),
1280 textview->scrolledwin_mb);
1281 gtk_container_add(GTK_CONTAINER(parent),
1282 textview->scrolledwin_sb);
1284 textview->text = textview->text_sb;
1285 textview->text_is_mb = FALSE;
1286 } else if (!textview->text_is_mb && use_fontset) {
1289 parent = textview->scrolledwin_sb->parent;
1290 gtkut_container_remove(GTK_CONTAINER(parent),
1291 textview->scrolledwin_sb);
1292 gtk_container_add(GTK_CONTAINER(parent),
1293 textview->scrolledwin_mb);
1295 textview->text = textview->text_mb;
1296 textview->text_is_mb = TRUE;
1299 if (prefs_common.textfont) {
1304 text_mb_font->ascent = text_mb_font_orig_ascent;
1305 text_mb_font->descent = text_mb_font_orig_descent;
1307 font = gdk_fontset_load(prefs_common.textfont);
1308 if (font && text_mb_font != font) {
1310 gdk_font_unref(text_mb_font);
1311 text_mb_font = font;
1312 text_mb_font_orig_ascent = font->ascent;
1313 text_mb_font_orig_descent = font->descent;
1317 text_sb_font->ascent = text_sb_font_orig_ascent;
1318 text_sb_font->descent = text_sb_font_orig_descent;
1321 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1323 font = gtkut_font_load_from_fontset
1324 (prefs_common.textfont);
1325 if (font && text_sb_font != font) {
1327 gdk_font_unref(text_sb_font);
1328 text_sb_font = font;
1329 text_sb_font_orig_ascent = font->ascent;
1330 text_sb_font_orig_descent = font->descent;
1335 gint ascent, descent;
1337 descent = prefs_common.line_space / 2;
1338 ascent = prefs_common.line_space - descent;
1339 font->ascent += ascent;
1340 font->descent += descent;
1342 if (textview->msgfont)
1343 gdk_font_unref(textview->msgfont);
1344 textview->msgfont = font;
1349 if (!textview->boldfont && prefs_common.boldfont)
1350 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1352 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1368 H_ORGANIZATION = 11,
1371 void textview_set_position(TextView *textview, gint pos)
1375 gtk_stext_get_length(GTK_STEXT(textview->text));
1377 textview->cur_pos = pos;
1381 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1383 gchar buf[BUFFSIZE];
1384 GPtrArray *headers, *sorted_headers;
1385 GSList *disphdr_list;
1389 g_return_val_if_fail(fp != NULL, NULL);
1391 if (textview->show_all_headers)
1392 return procheader_get_header_array_asis(fp);
1394 if (!prefs_common.display_header) {
1395 while (fgets(buf, sizeof(buf), fp) != NULL)
1396 if (buf[0] == '\r' || buf[0] == '\n') break;
1400 headers = procheader_get_header_array_asis(fp);
1402 sorted_headers = g_ptr_array_new();
1404 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1405 disphdr_list = disphdr_list->next) {
1406 DisplayHeaderProp *dp =
1407 (DisplayHeaderProp *)disphdr_list->data;
1409 for (i = 0; i < headers->len; i++) {
1410 header = g_ptr_array_index(headers, i);
1412 if (procheader_headername_equal(header->name,
1415 procheader_header_free(header);
1417 g_ptr_array_add(sorted_headers, header);
1419 g_ptr_array_remove_index(headers, i);
1425 if (prefs_common.show_other_header) {
1426 for (i = 0; i < headers->len; i++) {
1427 header = g_ptr_array_index(headers, i);
1428 g_ptr_array_add(sorted_headers, header);
1430 g_ptr_array_free(headers, TRUE);
1432 procheader_header_array_destroy(headers);
1435 return sorted_headers;
1438 static void textview_show_header(TextView *textview, GPtrArray *headers)
1440 GtkSText *text = GTK_STEXT(textview->text);
1444 g_return_if_fail(headers != NULL);
1446 gtk_stext_freeze(text);
1448 for (i = 0; i < headers->len; i++) {
1449 header = g_ptr_array_index(headers, i);
1450 g_return_if_fail(header->name != NULL);
1452 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1454 if (header->name[strlen(header->name) - 1] != ' ')
1455 gtk_stext_insert(text, textview->boldfont,
1456 NULL, NULL, " ", 1);
1458 if (procheader_headername_equal(header->name, "Subject") ||
1459 procheader_headername_equal(header->name, "From") ||
1460 procheader_headername_equal(header->name, "To") ||
1461 procheader_headername_equal(header->name, "Cc"))
1462 unfold_line(header->body);
1464 if (textview->text_is_mb == TRUE)
1465 conv_unreadable_locale(header->body);
1467 if (prefs_common.enable_color &&
1468 (procheader_headername_equal(header->name, "X-Mailer") ||
1469 procheader_headername_equal(header->name,
1471 strstr(header->body, "Sylpheed") != NULL)
1472 gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1474 else if (prefs_common.enable_color) {
1475 textview_make_clickable_parts(textview,
1476 NULL, NULL, &uri_color,
1479 textview_make_clickable_parts(textview,
1483 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1486 gtk_stext_thaw(text);
1489 gboolean textview_search_string(TextView *textview, const gchar *str,
1492 GtkSText *text = GTK_STEXT(textview->text);
1497 gboolean found = FALSE;
1499 g_return_val_if_fail(str != NULL, FALSE);
1501 wcs = strdup_mbstowcs(str);
1502 g_return_val_if_fail(wcs != NULL, FALSE);
1504 pos = textview->cur_pos;
1505 if (pos < textview->body_pos)
1506 pos = textview->body_pos;
1507 text_len = gtk_stext_get_length(text);
1508 if (text_len - pos < len) {
1513 for (; pos < text_len; pos++) {
1514 if (text_len - pos < len) break;
1515 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1517 gtk_widget_hide(GTK_WIDGET(textview->scrolledwin));
1518 gtk_editable_set_position(GTK_EDITABLE(text),
1520 gtk_editable_select_region(GTK_EDITABLE(text),
1522 gtk_widget_show(GTK_WIDGET(textview->scrolledwin));
1523 textview_set_position(textview, pos + len);
1527 if (text_len - pos == len) break;
1534 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1537 GtkSText *text = GTK_STEXT(textview->text);
1542 gboolean found = FALSE;
1544 g_return_val_if_fail(str != NULL, FALSE);
1546 wcs = strdup_mbstowcs(str);
1547 g_return_val_if_fail(wcs != NULL, FALSE);
1549 pos = textview->cur_pos;
1550 text_len = gtk_stext_get_length(text);
1551 if (text_len - textview->body_pos < len) {
1555 if (pos <= textview->body_pos || text_len - pos < len)
1556 pos = text_len - len;
1558 for (; pos >= textview->body_pos; pos--) {
1559 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1561 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1562 gtk_editable_select_region(GTK_EDITABLE(text),
1564 textview_set_position(textview, pos - 1);
1568 if (pos == textview->body_pos) break;
1575 void textview_scroll_one_line(TextView *textview, gboolean up)
1577 GtkSText *text = GTK_STEXT(textview->text);
1580 if (prefs_common.enable_smooth_scroll) {
1581 textview_smooth_scroll_one_line(textview, up);
1586 upper = text->vadj->upper - text->vadj->page_size;
1587 if (text->vadj->value < upper) {
1588 text->vadj->value +=
1589 text->vadj->step_increment * 4;
1591 MIN(text->vadj->value, upper);
1592 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1596 if (text->vadj->value > 0.0) {
1597 text->vadj->value -=
1598 text->vadj->step_increment * 4;
1600 MAX(text->vadj->value, 0.0);
1601 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1607 gboolean textview_scroll_page(TextView *textview, gboolean up)
1609 GtkSText *text = GTK_STEXT(textview->text);
1613 if (prefs_common.enable_smooth_scroll)
1614 return textview_smooth_scroll_page(textview, up);
1616 if (prefs_common.scroll_halfpage)
1617 page_incr = text->vadj->page_increment / 2;
1619 page_incr = text->vadj->page_increment;
1622 upper = text->vadj->upper - text->vadj->page_size;
1623 if (text->vadj->value < upper) {
1624 text->vadj->value += page_incr;
1625 text->vadj->value = MIN(text->vadj->value, upper);
1626 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1631 if (text->vadj->value > 0.0) {
1632 text->vadj->value -= page_incr;
1633 text->vadj->value = MAX(text->vadj->value, 0.0);
1634 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1643 static void textview_smooth_scroll_do(TextView *textview,
1644 gfloat old_value, gfloat last_value,
1647 GtkSText *text = GTK_STEXT(textview->text);
1652 if (old_value < last_value) {
1653 change_value = last_value - old_value;
1656 change_value = old_value - last_value;
1660 gdk_key_repeat_disable();
1662 for (i = step; i <= change_value; i += step) {
1663 text->vadj->value = old_value + (up ? -i : i);
1664 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1668 text->vadj->value = last_value;
1669 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1671 gdk_key_repeat_restore();
1674 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1676 GtkSText *text = GTK_STEXT(textview->text);
1682 upper = text->vadj->upper - text->vadj->page_size;
1683 if (text->vadj->value < upper) {
1684 old_value = text->vadj->value;
1685 last_value = text->vadj->value +
1686 text->vadj->step_increment * 4;
1687 last_value = MIN(last_value, upper);
1689 textview_smooth_scroll_do(textview, old_value,
1691 prefs_common.scroll_step);
1694 if (text->vadj->value > 0.0) {
1695 old_value = text->vadj->value;
1696 last_value = text->vadj->value -
1697 text->vadj->step_increment * 4;
1698 last_value = MAX(last_value, 0.0);
1700 textview_smooth_scroll_do(textview, old_value,
1702 prefs_common.scroll_step);
1707 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1709 GtkSText *text = GTK_STEXT(textview->text);
1715 if (prefs_common.scroll_halfpage)
1716 page_incr = text->vadj->page_increment / 2;
1718 page_incr = text->vadj->page_increment;
1721 upper = text->vadj->upper - text->vadj->page_size;
1722 if (text->vadj->value < upper) {
1723 old_value = text->vadj->value;
1724 last_value = text->vadj->value + page_incr;
1725 last_value = MIN(last_value, upper);
1727 textview_smooth_scroll_do(textview, old_value,
1729 prefs_common.scroll_step);
1733 if (text->vadj->value > 0.0) {
1734 old_value = text->vadj->value;
1735 last_value = text->vadj->value - page_incr;
1736 last_value = MAX(last_value, 0.0);
1738 textview_smooth_scroll_do(textview, old_value,
1740 prefs_common.scroll_step);
1748 #define KEY_PRESS_EVENT_STOP() \
1749 if (gtk_signal_n_emissions_by_name \
1750 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1751 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1752 "key_press_event"); \
1755 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1758 SummaryView *summaryview = NULL;
1759 MessageView *messageview = textview->messageview;
1761 if (!event) return FALSE;
1762 if (messageview->mainwin)
1763 summaryview = messageview->mainwin->summaryview;
1765 switch (event->keyval) {
1780 summary_pass_key_press_event(summaryview, event);
1782 textview_scroll_page(textview, FALSE);
1785 textview_scroll_page(textview, TRUE);
1788 textview_scroll_one_line(textview,
1789 (event->state & GDK_MOD1_MASK) != 0);
1793 summary_pass_key_press_event(summaryview, event);
1802 if (messageview->type == MVIEW_MIME &&
1803 textview == messageview->mimeview->textview) {
1804 KEY_PRESS_EVENT_STOP();
1805 mimeview_pass_key_press_event(messageview->mimeview,
1812 event->window != messageview->mainwin->window->window) {
1813 GdkEventKey tmpev = *event;
1815 tmpev.window = messageview->mainwin->window->window;
1816 KEY_PRESS_EVENT_STOP();
1817 gtk_widget_event(messageview->mainwin->window,
1818 (GdkEvent *)&tmpev);
1826 static gint show_url_timeout_cb(gpointer data)
1828 TextView *textview = (TextView *)data;
1830 if (textview->messageview->mainwin)
1831 if (textview->show_url_msgid)
1832 gtk_statusbar_remove(GTK_STATUSBAR(
1833 textview->messageview->mainwin->statusbar),
1834 textview->messageview->mainwin->folderview_cid,
1835 textview->show_url_msgid);
1839 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1843 textview->last_buttonpress = event->type;
1847 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1851 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1854 ((event->button == 1)
1855 || event->button == 2 || event->button == 3)) {
1858 /* double click seems to set the cursor after the current
1859 * word. The cursor position needs fixing, otherwise the
1860 * last word of a clickable zone will not work */
1861 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1862 textview->cur_pos--;
1865 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1866 RemoteURI *uri = (RemoteURI *)cur->data;
1868 if (textview->cur_pos >= uri->start &&
1869 textview->cur_pos <= uri->end) {
1870 /* single click: display url in statusbar */
1871 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1872 if (textview->messageview->mainwin) {
1873 if (textview->show_url_msgid) {
1874 gtk_timeout_remove(textview->show_url_timeout_tag);
1875 gtk_statusbar_remove(GTK_STATUSBAR(
1876 textview->messageview->mainwin->statusbar),
1877 textview->messageview->mainwin->folderview_cid,
1878 textview->show_url_msgid);
1879 textview->show_url_msgid = 0;
1881 textview->show_url_msgid = gtk_statusbar_push(
1882 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1883 textview->messageview->mainwin->folderview_cid,
1885 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1886 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1889 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1890 if (event->button == 3) {
1891 gchar *fromname, *fromaddress;
1894 fromaddress = g_strdup(uri->uri + 7);
1895 /* Hiroyuki: please put this function in utils.c! */
1896 fromname = procheader_get_fromname(fromaddress);
1897 extract_address(fromaddress);
1898 g_message("adding from textview %s <%s>", fromname, fromaddress);
1899 /* Add to address book - Match */
1900 addressbook_add_contact( fromname, fromaddress, NULL );
1902 g_free(fromaddress);
1905 PrefsAccount *account = NULL;
1906 FolderItem *folder_item;
1908 if (textview->messageview && textview->messageview->mainwin
1909 && textview->messageview->mainwin->summaryview
1910 && textview->messageview->mainwin->summaryview->folder_item) {
1911 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1912 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1913 account = account_find_from_id(folder_item->prefs->default_account);
1915 compose_new(account, uri->uri + 7, NULL);
1919 if (!g_strncasecmp(uri->uri, "pgp:", 4)) {
1920 GtkAdjustment *pos = gtk_scrolled_window_get_vadjustment(
1921 GTK_SCROLLED_WINDOW(textview->scrolledwin));
1922 gfloat vpos = pos->value;
1923 mimeview_check_signature(textview->messageview->mimeview);
1924 /* scroll back where we were */
1925 gtk_adjustment_set_value(pos, vpos);
1930 prefs_common.uri_cmd);
1936 textview->last_buttonpress = event->type;
1940 static void textview_uri_list_remove_all(GSList *uri_list)
1944 for (cur = uri_list; cur != NULL; cur = cur->next) {
1946 g_free(((RemoteURI *)cur->data)->uri);
1951 g_slist_free(uri_list);