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 (textview->messageview->forced_charset)
333 charset = textview->messageview->forced_charset;
334 else if (prefs_common.force_charset)
335 charset = prefs_common.force_charset;
336 else if (mimeinfo->charset)
337 charset = mimeinfo->charset;
339 textview_set_font(textview, charset);
340 textview_clear(textview);
342 text = GTK_STEXT(textview->text);
344 gtk_stext_freeze(text);
346 if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) perror("fseek");
347 headers = textview_scan_header(textview, fp);
349 textview_show_header(textview, headers);
350 procheader_header_array_destroy(headers);
351 textview->body_pos = gtk_stext_get_length(text);
354 textview_add_parts(textview, mimeinfo, fp);
356 gtk_stext_thaw(text);
361 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
365 const gchar *boundary = NULL;
366 gint boundary_len = 0;
367 const gchar *charset = NULL;
368 GPtrArray *headers = NULL;
369 gboolean is_rfc822_part = FALSE;
371 g_return_if_fail(mimeinfo != NULL);
372 g_return_if_fail(fp != NULL);
374 if (mimeinfo->mime_type == MIME_MULTIPART) {
375 textview_clear(textview);
376 textview_add_parts(textview, mimeinfo, fp);
380 if (mimeinfo->parent && mimeinfo->parent->boundary) {
381 boundary = mimeinfo->parent->boundary;
382 boundary_len = strlen(boundary);
385 if (!boundary && (mimeinfo->mime_type == MIME_TEXT ||
386 mimeinfo->mime_type == MIME_TEXT_HTML ||
387 mimeinfo->mime_type == MIME_TEXT_ENRICHED)) {
389 if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0)
391 headers = textview_scan_header(textview, fp);
393 if (mimeinfo->mime_type == MIME_TEXT && mimeinfo->parent) {
395 MimeInfo *parent = mimeinfo->parent;
397 while (parent->parent) {
399 parent->main->mime_type ==
402 parent = parent->parent;
405 if ((fpos = ftell(fp)) < 0)
407 else if (fseek(fp, parent->fpos, SEEK_SET) < 0)
410 headers = textview_scan_header(textview, fp);
411 if (fseek(fp, fpos, SEEK_SET) < 0)
415 /* skip MIME part headers */
416 while (fgets(buf, sizeof(buf), fp) != NULL)
417 if (buf[0] == '\r' || buf[0] == '\n') break;
420 /* display attached RFC822 single text message */
421 if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
422 if (headers) procheader_header_array_destroy(headers);
423 if (!mimeinfo->sub) {
424 textview_clear(textview);
427 headers = textview_scan_header(textview, fp);
428 mimeinfo = mimeinfo->sub;
429 is_rfc822_part = TRUE;
432 if (textview->messageview->forced_charset)
433 charset = textview->messageview->forced_charset;
434 else if (prefs_common.force_charset)
435 charset = prefs_common.force_charset;
436 else if (mimeinfo->charset)
437 charset = mimeinfo->charset;
439 textview_set_font(textview, charset);
441 text = GTK_STEXT(textview->text);
443 gtk_stext_freeze(text);
444 textview_clear(textview);
447 textview_show_header(textview, headers);
448 procheader_header_array_destroy(headers);
449 textview->body_pos = gtk_stext_get_length(text);
451 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
454 if (mimeinfo->mime_type == MIME_MULTIPART || is_rfc822_part)
455 textview_add_parts(textview, mimeinfo, fp);
457 textview_write_body(textview, mimeinfo, fp, charset);
459 gtk_stext_thaw(text);
462 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
464 GtkSText *text = GTK_STEXT(textview->text);
466 const gchar *boundary = NULL;
467 gint boundary_len = 0;
468 const gchar *charset = NULL;
469 GPtrArray *headers = NULL;
471 g_return_if_fail(mimeinfo != NULL);
472 g_return_if_fail(fp != NULL);
474 if (mimeinfo->mime_type == MIME_MULTIPART) return;
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 (textview->messageview->forced_charset)
555 charset = textview->messageview->forced_charset;
556 else if (prefs_common.force_charset)
557 charset = prefs_common.force_charset;
558 else if (mimeinfo->charset)
559 charset = mimeinfo->charset;
560 textview_write_body(textview, mimeinfo, fp, charset);
563 gtk_stext_thaw(text);
566 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
570 g_return_if_fail(mimeinfo != NULL);
571 g_return_if_fail(fp != NULL);
573 level = mimeinfo->level;
576 textview_add_part(textview, mimeinfo, fp);
577 if (mimeinfo->parent && mimeinfo->parent->content_type &&
578 !strcasecmp(mimeinfo->parent->content_type,
579 "multipart/alternative"))
580 mimeinfo = mimeinfo->parent->next;
582 mimeinfo = procmime_mimeinfo_next(mimeinfo);
583 if (!mimeinfo || mimeinfo->level <= level)
588 #define TEXT_INSERT(str) \
589 gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
591 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
595 if (!partinfo) return;
597 textview_set_font(textview, NULL);
598 text = GTK_STEXT(textview->text);
599 textview_clear(textview);
601 gtk_stext_freeze(text);
603 TEXT_INSERT(_("To save this part, pop up the context menu with "));
604 TEXT_INSERT(_("right click and select `Save as...', "));
605 TEXT_INSERT(_("or press `y' key.\n\n"));
607 TEXT_INSERT(_("To display this part as a text message, select "));
608 TEXT_INSERT(_("`Display as text', or press `t' key.\n\n"));
610 TEXT_INSERT(_("To open this part with external program, select "));
611 TEXT_INSERT(_("`Open' or `Open with...', "));
612 TEXT_INSERT(_("or double-click, or click the center button, "));
613 TEXT_INSERT(_("or press `l' key."));
615 gtk_stext_thaw(text);
619 void textview_show_signature_part(TextView *textview, MimeInfo *partinfo)
623 if (!partinfo) return;
625 textview_set_font(textview, NULL);
626 text = GTK_STEXT(textview->text);
627 textview_clear(textview);
629 gtk_stext_freeze(text);
631 if (partinfo->sigstatus_full == NULL) {
632 TEXT_INSERT(_("This signature has not been checked yet.\n"));
633 TEXT_INSERT(_("To check it, pop up the context menu with\n"));
634 TEXT_INSERT(_("right click and select `Check signature'.\n"));
636 TEXT_INSERT(partinfo->sigstatus_full);
639 gtk_stext_thaw(text);
641 #endif /* USE_GPGME */
645 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
646 FILE *fp, const gchar *charset)
652 conv = conv_code_converter_new(charset);
654 tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
656 textview->is_in_signature = FALSE;
659 if (mimeinfo->mime_type == MIME_TEXT_HTML)
660 textview_show_html(textview, tmpfp, conv);
661 else if (mimeinfo->mime_type == MIME_TEXT_ENRICHED)
662 textview_show_ertf(textview, tmpfp, conv);
664 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
665 textview_write_line(textview, buf, conv);
669 conv_code_converter_destroy(conv);
672 static void textview_show_html(TextView *textview, FILE *fp,
678 parser = html_parser_new(fp, conv);
679 g_return_if_fail(parser != NULL);
681 while ((str = html_parse(parser)) != NULL) {
682 if (parser->state == HTML_HREF) {
683 /* first time : get and copy the URL */
684 if (parser->href == NULL) {
685 /* ALF - the sylpheed html parser returns an empty string,
686 * if still inside an <a>, but already parsed past HREF */
687 str = strtok(str, " ");
689 parser->href = strdup(str);
690 /* the URL may (or not) be followed by the
692 str = strtok(NULL, "");
696 textview_write_link(textview, str, parser->href, NULL);
698 textview_write_line(textview, str, NULL);
700 html_parser_destroy(parser);
703 static void textview_show_ertf(TextView *textview, FILE *fp,
709 parser = ertf_parser_new(fp, conv);
710 g_return_if_fail(parser != NULL);
712 while ((str = ertf_parse(parser)) != NULL) {
713 textview_write_line(textview, str, NULL);
716 ertf_parser_destroy(parser);
719 /* get_uri_part() - retrieves a URI starting from scanpos.
720 Returns TRUE if succesful */
721 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
722 const gchar **bp, const gchar **ep)
726 g_return_val_if_fail(start != NULL, FALSE);
727 g_return_val_if_fail(scanpos != NULL, FALSE);
728 g_return_val_if_fail(bp != NULL, FALSE);
729 g_return_val_if_fail(ep != NULL, FALSE);
733 /* find end point of URI */
734 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
735 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
739 /* no punctuation at end of string */
741 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
742 * should pass some URI type to this function and decide on that whether
743 * to perform punctuation stripping */
745 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
747 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
757 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
759 return g_strndup(bp, ep - bp);
762 /* valid mail address characters */
763 #define IS_RFC822_CHAR(ch) \
768 !strchr("(),;<>\"", (ch)))
770 /* alphabet and number within 7bit ASCII */
771 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
772 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
774 static GHashTable *create_domain_tab(void)
776 static const gchar *toplvl_domains [] = {
778 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
779 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
780 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
781 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
782 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
783 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
784 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
785 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
786 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
787 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
788 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
789 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
790 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
791 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
792 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
793 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
794 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
795 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
796 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
797 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
798 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
799 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
800 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
801 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
802 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
803 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
806 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
808 g_return_val_if_fail(htab, NULL);
809 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
810 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
814 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
816 const gint MAX_LVL_DOM_NAME_LEN = 6;
817 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
818 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
821 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
824 for (p = buf; p < m && first < last; *p++ = *first++)
828 return g_hash_table_lookup(tab, buf) != NULL;
831 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
832 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
833 const gchar **bp, const gchar **ep)
835 /* more complex than the uri part because we need to scan back and forward starting from
836 * the scan position. */
837 gboolean result = FALSE;
838 const gchar *bp_ = NULL;
839 const gchar *ep_ = NULL;
840 static GHashTable *dom_tab;
841 const gchar *last_dot = NULL;
842 const gchar *prelast_dot = NULL;
843 const gchar *last_tld_char = NULL;
845 /* the informative part of the email address (describing the name
846 * of the email address owner) may contain quoted parts. the
847 * closure stack stores the last encountered quotes. */
848 gchar closure_stack[128];
849 gchar *ptr = closure_stack;
851 g_return_val_if_fail(start != NULL, FALSE);
852 g_return_val_if_fail(scanpos != NULL, FALSE);
853 g_return_val_if_fail(bp != NULL, FALSE);
854 g_return_val_if_fail(ep != NULL, FALSE);
857 dom_tab = create_domain_tab();
858 g_return_val_if_fail(dom_tab, FALSE);
860 /* scan start of address */
861 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
864 /* TODO: should start with an alnum? */
866 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
869 if (bp_ != scanpos) {
870 /* scan end of address */
871 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
873 prelast_dot = last_dot;
875 if (*(last_dot + 1) == '.') {
876 if (prelast_dot == NULL)
878 last_dot = prelast_dot;
883 /* TODO: really should terminate with an alnum? */
884 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
888 if (last_dot == NULL)
891 last_dot = prelast_dot;
892 if (last_dot == NULL || (scanpos + 1 >= last_dot))
896 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
897 if (*last_tld_char == '?')
900 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
907 if (!result) return FALSE;
909 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
910 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
913 /* see if this is <bracketed>; in this case we also scan for the informative part. */
914 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
917 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
918 #define IN_STACK() (ptr > closure_stack)
919 /* has underrun check */
920 #define POP_STACK() if(IN_STACK()) --ptr
921 /* has overrun check */
922 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
923 /* has underrun check */
924 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
928 /* scan for the informative part. */
929 for (bp_ -= 2; bp_ >= start; bp_--) {
930 /* if closure on the stack keep scanning */
931 if (PEEK_STACK() == *bp_) {
935 if (*bp_ == '\'' || *bp_ == '"') {
940 /* if nothing in the closure stack, do the special conditions
941 * the following if..else expression simply checks whether
942 * a token is acceptable. if not acceptable, the clause
943 * should terminate the loop with a 'break' */
946 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
947 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
948 /* hyphens are allowed, but only in
950 } else if (!ispunct(*bp_)) {
951 /* but anything not being a punctiation
954 break; /* anything else is rejected */
967 /* scan forward (should start with an alnum) */
968 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
978 #undef IS_RFC822_CHAR
980 static gchar *make_email_string(const gchar *bp, const gchar *ep)
982 /* returns a mailto: URI; mailto: is also used to detect the
983 * uri type later on in the button_pressed signal handler */
987 tmp = g_strndup(bp, ep - bp);
988 result = g_strconcat("mailto:", tmp, NULL);
994 #define ADD_TXT_POS(bp_, ep_, pti_) \
995 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
997 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1000 g_warning("alloc error scanning URIs\n"); \
1001 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
1006 /* textview_make_clickable_parts() - colorizes clickable parts */
1007 static void textview_make_clickable_parts(TextView *textview,
1010 GdkColor *uri_color,
1011 const gchar *linebuf)
1013 /* parse table - in order of priority */
1015 const gchar *needle; /* token */
1017 /* token search function */
1018 gchar *(*search) (const gchar *haystack,
1019 const gchar *needle);
1020 /* part parsing function */
1021 gboolean (*parse) (const gchar *start,
1022 const gchar *scanpos,
1025 /* part to URI function */
1026 gchar *(*build_uri) (const gchar *bp,
1030 static struct table parser[] = {
1031 {"http://", strcasestr, get_uri_part, make_uri_string},
1032 {"https://", strcasestr, get_uri_part, make_uri_string},
1033 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1034 {"www.", strcasestr, get_uri_part, make_uri_string},
1035 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1036 {"@", strcasestr, get_email_part, make_email_string}
1038 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1041 const gchar *walk, *bp, *ep;
1044 const gchar *bp, *ep; /* text position */
1045 gint pti; /* index in parse table */
1046 struct txtpos *next; /* next */
1047 } head = {NULL, NULL, 0, NULL}, *last = &head;
1049 GtkSText *text = GTK_STEXT(textview->text);
1051 /* parse for clickable parts, and build a list of begin and end positions */
1052 for (walk = linebuf, n = 0;;) {
1053 gint last_index = PARSE_ELEMS;
1054 gchar *scanpos = NULL;
1056 /* FIXME: this looks phony. scanning for anything in the parse table */
1057 for (n = 0; n < PARSE_ELEMS; n++) {
1060 tmp = parser[n].search(walk, parser[n].needle);
1062 if (scanpos == NULL || tmp < scanpos) {
1070 /* check if URI can be parsed */
1071 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1072 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1073 ADD_TXT_POS(bp, ep, last_index);
1077 strlen(parser[last_index].needle);
1082 /* colorize this line */
1084 const gchar *normal_text = linebuf;
1087 for (last = head.next; last != NULL;
1088 normal_text = last->ep, last = last->next) {
1091 uri = g_new(RemoteURI, 1);
1092 if (last->bp - normal_text > 0)
1093 gtk_stext_insert(text, font,
1096 last->bp - normal_text);
1097 uri->uri = parser[last->pti].build_uri(last->bp,
1099 uri->start = gtk_stext_get_point(text);
1100 gtk_stext_insert(text, font, uri_color,
1101 NULL, last->bp, last->ep - last->bp);
1102 uri->end = gtk_stext_get_point(text);
1103 textview->uri_list =
1104 g_slist_append(textview->uri_list, uri);
1108 gtk_stext_insert(text, font, fg_color,
1109 NULL, normal_text, -1);
1111 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1116 static void textview_write_line(TextView *textview, const gchar *str,
1117 CodeConverter *conv)
1119 GtkSText *text = GTK_STEXT(textview->text);
1120 gchar buf[BUFFSIZE];
1122 gint quotelevel = -1;
1125 if (textview->text_is_mb)
1126 conv_localetodisp(buf, sizeof(buf), str);
1128 strncpy2(buf, str, sizeof(buf));
1129 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1130 conv_localetodisp(buf, sizeof(buf), str);
1131 else if (textview->text_is_mb)
1132 conv_unreadable_locale(buf);
1135 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1138 /* change color of quotation
1139 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1140 Up to 3 levels of quotations are detected, and each
1141 level is colored using a different color. */
1142 if (prefs_common.enable_color
1143 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1144 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1146 /* set up the correct foreground color */
1147 if (quotelevel > 2) {
1148 /* recycle colors */
1149 if (prefs_common.recycle_quote_colors)
1156 if (quotelevel == -1)
1159 fg_color = "e_colors[quotelevel];
1161 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1162 fg_color = &signature_color;
1163 textview->is_in_signature = TRUE;
1166 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1167 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1169 if (prefs_common.enable_color)
1170 textview_make_clickable_parts(textview, textview->msgfont,
1171 fg_color, &uri_color, buf);
1173 textview_make_clickable_parts(textview, textview->msgfont,
1174 fg_color, NULL, buf);
1177 void textview_write_link(TextView *textview, const gchar *str,
1178 const gchar *uri, CodeConverter *conv)
1180 GdkColor *link_color = NULL;
1181 GtkSText *text = GTK_STEXT(textview->text);
1182 gchar buf[BUFFSIZE];
1190 if (textview->text_is_mb)
1191 conv_localetodisp(buf, sizeof(buf), str);
1193 strncpy2(buf, str, sizeof(buf));
1194 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1195 conv_localetodisp(buf, sizeof(buf), str);
1196 else if (textview->text_is_mb)
1197 conv_unreadable_locale(buf);
1201 for (bufp = buf; isspace(*bufp); bufp++)
1202 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1204 if (prefs_common.enable_color) {
1205 link_color = &uri_color;
1207 r_uri = g_new(RemoteURI, 1);
1208 r_uri->uri = g_strdup(uri);
1209 r_uri->start = gtk_stext_get_point(text);
1210 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1211 r_uri->end = gtk_stext_get_point(text);
1212 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1215 void textview_clear(TextView *textview)
1217 GtkSText *text = GTK_STEXT(textview->text);
1219 gtk_stext_freeze(text);
1220 gtk_stext_set_point(text, 0);
1221 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1222 gtk_stext_thaw(text);
1224 textview_uri_list_remove_all(textview->uri_list);
1225 textview->uri_list = NULL;
1227 textview->body_pos = 0;
1228 textview->cur_pos = 0;
1231 void textview_destroy(TextView *textview)
1233 textview_uri_list_remove_all(textview->uri_list);
1234 textview->uri_list = NULL;
1236 if (!textview->scrolledwin_sb->parent)
1237 gtk_widget_destroy(textview->scrolledwin_sb);
1238 if (!textview->scrolledwin_mb->parent)
1239 gtk_widget_destroy(textview->scrolledwin_mb);
1241 if (textview->msgfont)
1242 gdk_font_unref(textview->msgfont);
1243 if (textview->boldfont)
1244 gdk_font_unref(textview->boldfont);
1249 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1251 textview->show_all_headers = all_headers;
1254 void textview_set_font(TextView *textview, const gchar *codeset)
1256 gboolean use_fontset = TRUE;
1258 /* In multi-byte mode, GtkSText can't display 8bit characters
1259 correctly, so it must be single-byte mode. */
1260 if (MB_CUR_MAX > 1) {
1261 if (codeset && conv_get_current_charset() != C_UTF_8) {
1262 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1263 !g_strcasecmp(codeset, "BALTIC"))
1264 use_fontset = FALSE;
1265 else if (conv_get_current_charset() != C_EUC_JP &&
1266 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1267 !g_strncasecmp(codeset, "CP", 2) ||
1268 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1269 use_fontset = FALSE;
1272 use_fontset = FALSE;
1274 if (textview->text_is_mb && !use_fontset) {
1277 parent = textview->scrolledwin_mb->parent;
1278 gtkut_container_remove(GTK_CONTAINER(parent),
1279 textview->scrolledwin_mb);
1280 gtk_container_add(GTK_CONTAINER(parent),
1281 textview->scrolledwin_sb);
1283 textview->text = textview->text_sb;
1284 textview->text_is_mb = FALSE;
1285 } else if (!textview->text_is_mb && use_fontset) {
1288 parent = textview->scrolledwin_sb->parent;
1289 gtkut_container_remove(GTK_CONTAINER(parent),
1290 textview->scrolledwin_sb);
1291 gtk_container_add(GTK_CONTAINER(parent),
1292 textview->scrolledwin_mb);
1294 textview->text = textview->text_mb;
1295 textview->text_is_mb = TRUE;
1298 if (prefs_common.textfont) {
1303 text_mb_font->ascent = text_mb_font_orig_ascent;
1304 text_mb_font->descent = text_mb_font_orig_descent;
1306 font = gdk_fontset_load(prefs_common.textfont);
1307 if (font && text_mb_font != font) {
1309 gdk_font_unref(text_mb_font);
1310 text_mb_font = font;
1311 text_mb_font_orig_ascent = font->ascent;
1312 text_mb_font_orig_descent = font->descent;
1316 text_sb_font->ascent = text_sb_font_orig_ascent;
1317 text_sb_font->descent = text_sb_font_orig_descent;
1320 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1322 font = gtkut_font_load_from_fontset
1323 (prefs_common.textfont);
1324 if (font && text_sb_font != font) {
1326 gdk_font_unref(text_sb_font);
1327 text_sb_font = font;
1328 text_sb_font_orig_ascent = font->ascent;
1329 text_sb_font_orig_descent = font->descent;
1334 gint ascent, descent;
1336 descent = prefs_common.line_space / 2;
1337 ascent = prefs_common.line_space - descent;
1338 font->ascent += ascent;
1339 font->descent += descent;
1341 if (textview->msgfont)
1342 gdk_font_unref(textview->msgfont);
1343 textview->msgfont = font;
1348 if (!textview->boldfont && prefs_common.boldfont)
1349 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1351 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1367 H_ORGANIZATION = 11,
1370 void textview_set_position(TextView *textview, gint pos)
1374 gtk_stext_get_length(GTK_STEXT(textview->text));
1376 textview->cur_pos = pos;
1380 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1382 gchar buf[BUFFSIZE];
1383 GPtrArray *headers, *sorted_headers;
1384 GSList *disphdr_list;
1388 g_return_val_if_fail(fp != NULL, NULL);
1390 if (textview->show_all_headers)
1391 return procheader_get_header_array_asis(fp);
1393 if (!prefs_common.display_header) {
1394 while (fgets(buf, sizeof(buf), fp) != NULL)
1395 if (buf[0] == '\r' || buf[0] == '\n') break;
1399 headers = procheader_get_header_array_asis(fp);
1401 sorted_headers = g_ptr_array_new();
1403 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1404 disphdr_list = disphdr_list->next) {
1405 DisplayHeaderProp *dp =
1406 (DisplayHeaderProp *)disphdr_list->data;
1408 for (i = 0; i < headers->len; i++) {
1409 header = g_ptr_array_index(headers, i);
1411 if (procheader_headername_equal(header->name,
1414 procheader_header_free(header);
1416 g_ptr_array_add(sorted_headers, header);
1418 g_ptr_array_remove_index(headers, i);
1424 if (prefs_common.show_other_header) {
1425 for (i = 0; i < headers->len; i++) {
1426 header = g_ptr_array_index(headers, i);
1427 g_ptr_array_add(sorted_headers, header);
1429 g_ptr_array_free(headers, TRUE);
1431 procheader_header_array_destroy(headers);
1434 return sorted_headers;
1437 static void textview_show_header(TextView *textview, GPtrArray *headers)
1439 GtkSText *text = GTK_STEXT(textview->text);
1443 g_return_if_fail(headers != NULL);
1445 gtk_stext_freeze(text);
1447 for (i = 0; i < headers->len; i++) {
1448 header = g_ptr_array_index(headers, i);
1449 g_return_if_fail(header->name != NULL);
1451 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1453 if (header->name[strlen(header->name) - 1] != ' ')
1454 gtk_stext_insert(text, textview->boldfont,
1455 NULL, NULL, " ", 1);
1457 if (procheader_headername_equal(header->name, "Subject") ||
1458 procheader_headername_equal(header->name, "From") ||
1459 procheader_headername_equal(header->name, "To") ||
1460 procheader_headername_equal(header->name, "Cc"))
1461 unfold_line(header->body);
1463 if (textview->text_is_mb == TRUE)
1464 conv_unreadable_locale(header->body);
1466 if (prefs_common.enable_color &&
1467 (procheader_headername_equal(header->name, "X-Mailer") ||
1468 procheader_headername_equal(header->name,
1470 strstr(header->body, "Sylpheed") != NULL)
1471 gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1473 else if (prefs_common.enable_color) {
1474 textview_make_clickable_parts(textview,
1475 NULL, NULL, &uri_color,
1478 textview_make_clickable_parts(textview,
1482 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1485 gtk_stext_thaw(text);
1488 gboolean textview_search_string(TextView *textview, const gchar *str,
1491 GtkSText *text = GTK_STEXT(textview->text);
1495 g_return_val_if_fail(str != NULL, FALSE);
1497 len = get_mbs_len(str);
1498 g_return_val_if_fail(len >= 0, FALSE);
1500 pos = textview->cur_pos;
1501 if (pos < textview->body_pos)
1502 pos = textview->body_pos;
1504 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1505 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1506 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1507 textview_set_position(textview, pos + len);
1514 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1517 GtkSText *text = GTK_STEXT(textview->text);
1522 gboolean found = FALSE;
1524 g_return_val_if_fail(str != NULL, FALSE);
1526 wcs = strdup_mbstowcs(str);
1527 g_return_val_if_fail(wcs != NULL, FALSE);
1529 pos = textview->cur_pos;
1530 text_len = gtk_stext_get_length(text);
1531 if (text_len - textview->body_pos < len) {
1535 if (pos <= textview->body_pos || text_len - pos < len)
1536 pos = text_len - len;
1538 for (; pos >= textview->body_pos; pos--) {
1539 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1541 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1542 gtk_editable_select_region(GTK_EDITABLE(text),
1544 textview_set_position(textview, pos - 1);
1548 if (pos == textview->body_pos) break;
1555 void textview_scroll_one_line(TextView *textview, gboolean up)
1557 GtkSText *text = GTK_STEXT(textview->text);
1560 if (prefs_common.enable_smooth_scroll) {
1561 textview_smooth_scroll_one_line(textview, up);
1566 upper = text->vadj->upper - text->vadj->page_size;
1567 if (text->vadj->value < upper) {
1568 text->vadj->value +=
1569 text->vadj->step_increment * 4;
1571 MIN(text->vadj->value, upper);
1572 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1576 if (text->vadj->value > 0.0) {
1577 text->vadj->value -=
1578 text->vadj->step_increment * 4;
1580 MAX(text->vadj->value, 0.0);
1581 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1587 gboolean textview_scroll_page(TextView *textview, gboolean up)
1589 GtkSText *text = GTK_STEXT(textview->text);
1593 if (prefs_common.enable_smooth_scroll)
1594 return textview_smooth_scroll_page(textview, up);
1596 if (prefs_common.scroll_halfpage)
1597 page_incr = text->vadj->page_increment / 2;
1599 page_incr = text->vadj->page_increment;
1602 upper = text->vadj->upper - text->vadj->page_size;
1603 if (text->vadj->value < upper) {
1604 text->vadj->value += page_incr;
1605 text->vadj->value = MIN(text->vadj->value, upper);
1606 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1611 if (text->vadj->value > 0.0) {
1612 text->vadj->value -= page_incr;
1613 text->vadj->value = MAX(text->vadj->value, 0.0);
1614 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1623 static void textview_smooth_scroll_do(TextView *textview,
1624 gfloat old_value, gfloat last_value,
1627 GtkSText *text = GTK_STEXT(textview->text);
1632 if (old_value < last_value) {
1633 change_value = last_value - old_value;
1636 change_value = old_value - last_value;
1640 gdk_key_repeat_disable();
1642 for (i = step; i <= change_value; i += step) {
1643 text->vadj->value = old_value + (up ? -i : i);
1644 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1648 text->vadj->value = last_value;
1649 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1651 gdk_key_repeat_restore();
1654 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1656 GtkSText *text = GTK_STEXT(textview->text);
1662 upper = text->vadj->upper - text->vadj->page_size;
1663 if (text->vadj->value < upper) {
1664 old_value = text->vadj->value;
1665 last_value = text->vadj->value +
1666 text->vadj->step_increment * 4;
1667 last_value = MIN(last_value, upper);
1669 textview_smooth_scroll_do(textview, old_value,
1671 prefs_common.scroll_step);
1674 if (text->vadj->value > 0.0) {
1675 old_value = text->vadj->value;
1676 last_value = text->vadj->value -
1677 text->vadj->step_increment * 4;
1678 last_value = MAX(last_value, 0.0);
1680 textview_smooth_scroll_do(textview, old_value,
1682 prefs_common.scroll_step);
1687 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1689 GtkSText *text = GTK_STEXT(textview->text);
1695 if (prefs_common.scroll_halfpage)
1696 page_incr = text->vadj->page_increment / 2;
1698 page_incr = text->vadj->page_increment;
1701 upper = text->vadj->upper - text->vadj->page_size;
1702 if (text->vadj->value < upper) {
1703 old_value = text->vadj->value;
1704 last_value = text->vadj->value + page_incr;
1705 last_value = MIN(last_value, upper);
1707 textview_smooth_scroll_do(textview, old_value,
1709 prefs_common.scroll_step);
1713 if (text->vadj->value > 0.0) {
1714 old_value = text->vadj->value;
1715 last_value = text->vadj->value - page_incr;
1716 last_value = MAX(last_value, 0.0);
1718 textview_smooth_scroll_do(textview, old_value,
1720 prefs_common.scroll_step);
1728 #define KEY_PRESS_EVENT_STOP() \
1729 if (gtk_signal_n_emissions_by_name \
1730 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1731 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1732 "key_press_event"); \
1735 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1738 SummaryView *summaryview = NULL;
1739 MessageView *messageview = textview->messageview;
1741 if (!event) return FALSE;
1742 if (messageview->mainwin)
1743 summaryview = messageview->mainwin->summaryview;
1745 switch (event->keyval) {
1760 summary_pass_key_press_event(summaryview, event);
1762 textview_scroll_page(textview, FALSE);
1765 textview_scroll_page(textview, TRUE);
1768 textview_scroll_one_line(textview,
1769 (event->state & GDK_MOD1_MASK) != 0);
1773 summary_pass_key_press_event(summaryview, event);
1782 if (messageview->type == MVIEW_MIME &&
1783 textview == messageview->mimeview->textview) {
1784 KEY_PRESS_EVENT_STOP();
1785 mimeview_pass_key_press_event(messageview->mimeview,
1792 event->window != messageview->mainwin->window->window) {
1793 GdkEventKey tmpev = *event;
1795 tmpev.window = messageview->mainwin->window->window;
1796 KEY_PRESS_EVENT_STOP();
1797 gtk_widget_event(messageview->mainwin->window,
1798 (GdkEvent *)&tmpev);
1806 static gint show_url_timeout_cb(gpointer data)
1808 TextView *textview = (TextView *)data;
1810 if (textview->messageview->mainwin)
1811 if (textview->show_url_msgid)
1812 gtk_statusbar_remove(GTK_STATUSBAR(
1813 textview->messageview->mainwin->statusbar),
1814 textview->messageview->mainwin->folderview_cid,
1815 textview->show_url_msgid);
1819 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1823 textview->last_buttonpress = event->type;
1827 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1831 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1834 ((event->button == 1)
1835 || event->button == 2 || event->button == 3)) {
1838 /* double click seems to set the cursor after the current
1839 * word. The cursor position needs fixing, otherwise the
1840 * last word of a clickable zone will not work */
1841 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1842 textview->cur_pos--;
1845 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1846 RemoteURI *uri = (RemoteURI *)cur->data;
1848 if (textview->cur_pos >= uri->start &&
1849 textview->cur_pos <= uri->end) {
1850 /* single click: display url in statusbar */
1851 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1852 if (textview->messageview->mainwin) {
1853 if (textview->show_url_msgid) {
1854 gtk_timeout_remove(textview->show_url_timeout_tag);
1855 gtk_statusbar_remove(GTK_STATUSBAR(
1856 textview->messageview->mainwin->statusbar),
1857 textview->messageview->mainwin->folderview_cid,
1858 textview->show_url_msgid);
1859 textview->show_url_msgid = 0;
1861 textview->show_url_msgid = gtk_statusbar_push(
1862 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1863 textview->messageview->mainwin->folderview_cid,
1865 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1866 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1869 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1870 if (event->button == 3) {
1871 gchar *fromname, *fromaddress;
1874 fromaddress = g_strdup(uri->uri + 7);
1875 /* Hiroyuki: please put this function in utils.c! */
1876 fromname = procheader_get_fromname(fromaddress);
1877 extract_address(fromaddress);
1878 g_message("adding from textview %s <%s>", fromname, fromaddress);
1879 /* Add to address book - Match */
1880 addressbook_add_contact( fromname, fromaddress, NULL );
1882 g_free(fromaddress);
1885 PrefsAccount *account = NULL;
1886 FolderItem *folder_item;
1888 if (textview->messageview && textview->messageview->mainwin
1889 && textview->messageview->mainwin->summaryview
1890 && textview->messageview->mainwin->summaryview->folder_item) {
1891 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1892 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1893 account = account_find_from_id(folder_item->prefs->default_account);
1895 compose_new(account, uri->uri + 7, NULL);
1899 if (!g_strncasecmp(uri->uri, "pgp:", 4)) {
1900 GtkAdjustment *pos = gtk_scrolled_window_get_vadjustment(
1901 GTK_SCROLLED_WINDOW(textview->scrolledwin));
1902 gfloat vpos = pos->value;
1903 mimeview_check_signature(textview->messageview->mimeview);
1904 /* scroll back where we were */
1905 gtk_adjustment_set_value(pos, vpos);
1910 prefs_common.uri_cmd);
1916 textview->last_buttonpress = event->type;
1920 static void textview_uri_list_remove_all(GSList *uri_list)
1924 for (cur = uri_list; cur != NULL; cur = cur->next) {
1926 g_free(((RemoteURI *)cur->data)->uri);
1931 g_slist_free(uri_list);