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 open this part with external program, select "));
609 TEXT_INSERT(_("`Open' or `Open with...', "));
610 TEXT_INSERT(_("or double-click, or click the center button, "));
611 TEXT_INSERT(_("or press `l' key."));
613 gtk_stext_thaw(text);
617 void textview_show_signature_part(TextView *textview, MimeInfo *partinfo)
621 if (!partinfo) return;
623 textview_set_font(textview, NULL);
624 text = GTK_STEXT(textview->text);
625 textview_clear(textview);
627 gtk_stext_freeze(text);
629 if (partinfo->sigstatus_full == NULL) {
630 TEXT_INSERT(_("This signature has not been checked yet.\n"));
631 TEXT_INSERT(_("To check it, pop up the context menu with\n"));
632 TEXT_INSERT(_("right click and select `Check signature'.\n"));
634 TEXT_INSERT(partinfo->sigstatus_full);
637 gtk_stext_thaw(text);
639 #endif /* USE_GPGME */
643 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
644 FILE *fp, const gchar *charset)
650 conv = conv_code_converter_new(charset);
652 tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
654 textview->is_in_signature = FALSE;
657 if (mimeinfo->mime_type == MIME_TEXT_HTML)
658 textview_show_html(textview, tmpfp, conv);
659 else if (mimeinfo->mime_type == MIME_TEXT_ENRICHED)
660 textview_show_ertf(textview, tmpfp, conv);
662 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
663 textview_write_line(textview, buf, conv);
667 conv_code_converter_destroy(conv);
670 static void textview_show_html(TextView *textview, FILE *fp,
676 parser = html_parser_new(fp, conv);
677 g_return_if_fail(parser != NULL);
679 while ((str = html_parse(parser)) != NULL) {
680 if (parser->state == HTML_HREF) {
681 /* first time : get and copy the URL */
682 if (parser->href == NULL) {
683 /* ALF - the sylpheed html parser returns an empty string,
684 * if still inside an <a>, but already parsed past HREF */
685 str = strtok(str, " ");
687 parser->href = strdup(str);
688 /* the URL may (or not) be followed by the
690 str = strtok(NULL, "");
694 textview_write_link(textview, str, parser->href, NULL);
696 textview_write_line(textview, str, NULL);
698 html_parser_destroy(parser);
701 static void textview_show_ertf(TextView *textview, FILE *fp,
707 parser = ertf_parser_new(fp, conv);
708 g_return_if_fail(parser != NULL);
710 while ((str = ertf_parse(parser)) != NULL) {
711 textview_write_line(textview, str, NULL);
714 ertf_parser_destroy(parser);
717 /* get_uri_part() - retrieves a URI starting from scanpos.
718 Returns TRUE if succesful */
719 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
720 const gchar **bp, const gchar **ep)
724 g_return_val_if_fail(start != NULL, FALSE);
725 g_return_val_if_fail(scanpos != NULL, FALSE);
726 g_return_val_if_fail(bp != NULL, FALSE);
727 g_return_val_if_fail(ep != NULL, FALSE);
731 /* find end point of URI */
732 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
733 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
737 /* no punctuation at end of string */
739 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
740 * should pass some URI type to this function and decide on that whether
741 * to perform punctuation stripping */
743 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
745 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
755 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
757 return g_strndup(bp, ep - bp);
760 /* valid mail address characters */
761 #define IS_RFC822_CHAR(ch) \
766 !strchr("(),;<>\"", (ch)))
768 /* alphabet and number within 7bit ASCII */
769 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
770 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
772 static GHashTable *create_domain_tab(void)
774 static const gchar *toplvl_domains [] = {
776 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
777 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
778 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
779 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
780 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
781 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
782 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
783 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
784 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
785 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
786 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
787 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
788 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
789 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
790 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
791 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
792 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
793 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
794 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
795 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
796 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
797 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
798 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
799 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
800 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
801 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
804 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
806 g_return_val_if_fail(htab, NULL);
807 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
808 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
812 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
814 const gint MAX_LVL_DOM_NAME_LEN = 6;
815 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
816 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
819 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
822 for (p = buf; p < m && first < last; *p++ = *first++)
826 return g_hash_table_lookup(tab, buf) != NULL;
829 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
830 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
831 const gchar **bp, const gchar **ep)
833 /* more complex than the uri part because we need to scan back and forward starting from
834 * the scan position. */
835 gboolean result = FALSE;
836 const gchar *bp_ = NULL;
837 const gchar *ep_ = NULL;
838 static GHashTable *dom_tab;
839 const gchar *last_dot = NULL;
840 const gchar *prelast_dot = NULL;
841 const gchar *last_tld_char = NULL;
843 /* the informative part of the email address (describing the name
844 * of the email address owner) may contain quoted parts. the
845 * closure stack stores the last encountered quotes. */
846 gchar closure_stack[128];
847 gchar *ptr = closure_stack;
849 g_return_val_if_fail(start != NULL, FALSE);
850 g_return_val_if_fail(scanpos != NULL, FALSE);
851 g_return_val_if_fail(bp != NULL, FALSE);
852 g_return_val_if_fail(ep != NULL, FALSE);
855 dom_tab = create_domain_tab();
856 g_return_val_if_fail(dom_tab, FALSE);
858 /* scan start of address */
859 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
862 /* TODO: should start with an alnum? */
864 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
867 if (bp_ != scanpos) {
868 /* scan end of address */
869 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
871 prelast_dot = last_dot;
873 if (*(last_dot + 1) == '.') {
874 if (prelast_dot == NULL)
876 last_dot = prelast_dot;
881 /* TODO: really should terminate with an alnum? */
882 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
886 if (last_dot == NULL)
889 last_dot = prelast_dot;
890 if (last_dot == NULL || (scanpos + 1 >= last_dot))
894 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
895 if (*last_tld_char == '?')
898 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
905 if (!result) return FALSE;
907 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
908 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
911 /* see if this is <bracketed>; in this case we also scan for the informative part. */
912 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
915 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
916 #define IN_STACK() (ptr > closure_stack)
917 /* has underrun check */
918 #define POP_STACK() if(IN_STACK()) --ptr
919 /* has overrun check */
920 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
921 /* has underrun check */
922 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
926 /* scan for the informative part. */
927 for (bp_ -= 2; bp_ >= start; bp_--) {
928 /* if closure on the stack keep scanning */
929 if (PEEK_STACK() == *bp_) {
933 if (*bp_ == '\'' || *bp_ == '"') {
938 /* if nothing in the closure stack, do the special conditions
939 * the following if..else expression simply checks whether
940 * a token is acceptable. if not acceptable, the clause
941 * should terminate the loop with a 'break' */
944 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
945 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
946 /* hyphens are allowed, but only in
948 } else if (!ispunct(*bp_)) {
949 /* but anything not being a punctiation
952 break; /* anything else is rejected */
965 /* scan forward (should start with an alnum) */
966 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
976 #undef IS_RFC822_CHAR
978 static gchar *make_email_string(const gchar *bp, const gchar *ep)
980 /* returns a mailto: URI; mailto: is also used to detect the
981 * uri type later on in the button_pressed signal handler */
985 tmp = g_strndup(bp, ep - bp);
986 result = g_strconcat("mailto:", tmp, NULL);
992 #define ADD_TXT_POS(bp_, ep_, pti_) \
993 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
995 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
998 g_warning("alloc error scanning URIs\n"); \
999 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
1004 /* textview_make_clickable_parts() - colorizes clickable parts */
1005 static void textview_make_clickable_parts(TextView *textview,
1008 GdkColor *uri_color,
1009 const gchar *linebuf)
1011 /* parse table - in order of priority */
1013 const gchar *needle; /* token */
1015 /* token search function */
1016 gchar *(*search) (const gchar *haystack,
1017 const gchar *needle);
1018 /* part parsing function */
1019 gboolean (*parse) (const gchar *start,
1020 const gchar *scanpos,
1023 /* part to URI function */
1024 gchar *(*build_uri) (const gchar *bp,
1028 static struct table parser[] = {
1029 {"http://", strcasestr, get_uri_part, make_uri_string},
1030 {"https://", strcasestr, get_uri_part, make_uri_string},
1031 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1032 {"www.", strcasestr, get_uri_part, make_uri_string},
1033 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1034 {"@", strcasestr, get_email_part, make_email_string}
1036 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1039 const gchar *walk, *bp, *ep;
1042 const gchar *bp, *ep; /* text position */
1043 gint pti; /* index in parse table */
1044 struct txtpos *next; /* next */
1045 } head = {NULL, NULL, 0, NULL}, *last = &head;
1047 GtkSText *text = GTK_STEXT(textview->text);
1049 /* parse for clickable parts, and build a list of begin and end positions */
1050 for (walk = linebuf, n = 0;;) {
1051 gint last_index = PARSE_ELEMS;
1052 gchar *scanpos = NULL;
1054 /* FIXME: this looks phony. scanning for anything in the parse table */
1055 for (n = 0; n < PARSE_ELEMS; n++) {
1058 tmp = parser[n].search(walk, parser[n].needle);
1060 if (scanpos == NULL || tmp < scanpos) {
1068 /* check if URI can be parsed */
1069 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1070 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1071 ADD_TXT_POS(bp, ep, last_index);
1075 strlen(parser[last_index].needle);
1080 /* colorize this line */
1082 const gchar *normal_text = linebuf;
1085 for (last = head.next; last != NULL;
1086 normal_text = last->ep, last = last->next) {
1089 uri = g_new(RemoteURI, 1);
1090 if (last->bp - normal_text > 0)
1091 gtk_stext_insert(text, font,
1094 last->bp - normal_text);
1095 uri->uri = parser[last->pti].build_uri(last->bp,
1097 uri->start = gtk_stext_get_point(text);
1098 gtk_stext_insert(text, font, uri_color,
1099 NULL, last->bp, last->ep - last->bp);
1100 uri->end = gtk_stext_get_point(text);
1101 textview->uri_list =
1102 g_slist_append(textview->uri_list, uri);
1106 gtk_stext_insert(text, font, fg_color,
1107 NULL, normal_text, -1);
1109 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1114 static void textview_write_line(TextView *textview, const gchar *str,
1115 CodeConverter *conv)
1117 GtkSText *text = GTK_STEXT(textview->text);
1118 gchar buf[BUFFSIZE];
1120 gint quotelevel = -1;
1123 if (textview->text_is_mb)
1124 conv_localetodisp(buf, sizeof(buf), str);
1126 strncpy2(buf, str, sizeof(buf));
1127 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1128 conv_localetodisp(buf, sizeof(buf), str);
1129 else if (textview->text_is_mb)
1130 conv_unreadable_locale(buf);
1133 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1136 /* change color of quotation
1137 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1138 Up to 3 levels of quotations are detected, and each
1139 level is colored using a different color. */
1140 if (prefs_common.enable_color
1141 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1142 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1144 /* set up the correct foreground color */
1145 if (quotelevel > 2) {
1146 /* recycle colors */
1147 if (prefs_common.recycle_quote_colors)
1154 if (quotelevel == -1)
1157 fg_color = "e_colors[quotelevel];
1159 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1160 fg_color = &signature_color;
1161 textview->is_in_signature = TRUE;
1164 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1165 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1167 if (prefs_common.enable_color)
1168 textview_make_clickable_parts(textview, textview->msgfont,
1169 fg_color, &uri_color, buf);
1171 textview_make_clickable_parts(textview, textview->msgfont,
1172 fg_color, NULL, buf);
1175 void textview_write_link(TextView *textview, const gchar *str,
1176 const gchar *uri, CodeConverter *conv)
1178 GdkColor *link_color = NULL;
1179 GtkSText *text = GTK_STEXT(textview->text);
1180 gchar buf[BUFFSIZE];
1188 if (textview->text_is_mb)
1189 conv_localetodisp(buf, sizeof(buf), str);
1191 strncpy2(buf, str, sizeof(buf));
1192 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1193 conv_localetodisp(buf, sizeof(buf), str);
1194 else if (textview->text_is_mb)
1195 conv_unreadable_locale(buf);
1199 for (bufp = buf; isspace(*bufp); bufp++)
1200 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1202 if (prefs_common.enable_color) {
1203 link_color = &uri_color;
1205 r_uri = g_new(RemoteURI, 1);
1206 r_uri->uri = g_strdup(uri);
1207 r_uri->start = gtk_stext_get_point(text);
1208 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1209 r_uri->end = gtk_stext_get_point(text);
1210 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1213 void textview_clear(TextView *textview)
1215 GtkSText *text = GTK_STEXT(textview->text);
1217 gtk_stext_freeze(text);
1218 gtk_stext_set_point(text, 0);
1219 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1220 gtk_stext_thaw(text);
1222 textview_uri_list_remove_all(textview->uri_list);
1223 textview->uri_list = NULL;
1225 textview->body_pos = 0;
1226 textview->cur_pos = 0;
1229 void textview_destroy(TextView *textview)
1231 textview_uri_list_remove_all(textview->uri_list);
1232 textview->uri_list = NULL;
1234 if (!textview->scrolledwin_sb->parent)
1235 gtk_widget_destroy(textview->scrolledwin_sb);
1236 if (!textview->scrolledwin_mb->parent)
1237 gtk_widget_destroy(textview->scrolledwin_mb);
1239 if (textview->msgfont)
1240 gdk_font_unref(textview->msgfont);
1241 if (textview->boldfont)
1242 gdk_font_unref(textview->boldfont);
1247 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1249 textview->show_all_headers = all_headers;
1252 void textview_set_font(TextView *textview, const gchar *codeset)
1254 gboolean use_fontset = TRUE;
1256 /* In multi-byte mode, GtkSText can't display 8bit characters
1257 correctly, so it must be single-byte mode. */
1258 if (MB_CUR_MAX > 1) {
1259 if (codeset && conv_get_current_charset() != C_UTF_8) {
1260 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1261 !g_strcasecmp(codeset, "BALTIC"))
1262 use_fontset = FALSE;
1263 else if (conv_get_current_charset() != C_EUC_JP &&
1264 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1265 !g_strncasecmp(codeset, "CP", 2) ||
1266 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1267 use_fontset = FALSE;
1270 use_fontset = FALSE;
1272 if (textview->text_is_mb && !use_fontset) {
1275 parent = textview->scrolledwin_mb->parent;
1276 gtkut_container_remove(GTK_CONTAINER(parent),
1277 textview->scrolledwin_mb);
1278 gtk_container_add(GTK_CONTAINER(parent),
1279 textview->scrolledwin_sb);
1281 textview->text = textview->text_sb;
1282 textview->text_is_mb = FALSE;
1283 } else if (!textview->text_is_mb && use_fontset) {
1286 parent = textview->scrolledwin_sb->parent;
1287 gtkut_container_remove(GTK_CONTAINER(parent),
1288 textview->scrolledwin_sb);
1289 gtk_container_add(GTK_CONTAINER(parent),
1290 textview->scrolledwin_mb);
1292 textview->text = textview->text_mb;
1293 textview->text_is_mb = TRUE;
1296 if (prefs_common.textfont) {
1301 text_mb_font->ascent = text_mb_font_orig_ascent;
1302 text_mb_font->descent = text_mb_font_orig_descent;
1304 font = gdk_fontset_load(prefs_common.textfont);
1305 if (font && text_mb_font != font) {
1307 gdk_font_unref(text_mb_font);
1308 text_mb_font = font;
1309 text_mb_font_orig_ascent = font->ascent;
1310 text_mb_font_orig_descent = font->descent;
1314 text_sb_font->ascent = text_sb_font_orig_ascent;
1315 text_sb_font->descent = text_sb_font_orig_descent;
1318 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1320 font = gtkut_font_load_from_fontset
1321 (prefs_common.textfont);
1322 if (font && text_sb_font != font) {
1324 gdk_font_unref(text_sb_font);
1325 text_sb_font = font;
1326 text_sb_font_orig_ascent = font->ascent;
1327 text_sb_font_orig_descent = font->descent;
1332 gint ascent, descent;
1334 descent = prefs_common.line_space / 2;
1335 ascent = prefs_common.line_space - descent;
1336 font->ascent += ascent;
1337 font->descent += descent;
1339 if (textview->msgfont)
1340 gdk_font_unref(textview->msgfont);
1341 textview->msgfont = font;
1346 if (!textview->boldfont && prefs_common.boldfont)
1347 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1349 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1365 H_ORGANIZATION = 11,
1368 void textview_set_position(TextView *textview, gint pos)
1372 gtk_stext_get_length(GTK_STEXT(textview->text));
1374 textview->cur_pos = pos;
1378 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1380 gchar buf[BUFFSIZE];
1381 GPtrArray *headers, *sorted_headers;
1382 GSList *disphdr_list;
1386 g_return_val_if_fail(fp != NULL, NULL);
1388 if (textview->show_all_headers)
1389 return procheader_get_header_array_asis(fp);
1391 if (!prefs_common.display_header) {
1392 while (fgets(buf, sizeof(buf), fp) != NULL)
1393 if (buf[0] == '\r' || buf[0] == '\n') break;
1397 headers = procheader_get_header_array_asis(fp);
1399 sorted_headers = g_ptr_array_new();
1401 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1402 disphdr_list = disphdr_list->next) {
1403 DisplayHeaderProp *dp =
1404 (DisplayHeaderProp *)disphdr_list->data;
1406 for (i = 0; i < headers->len; i++) {
1407 header = g_ptr_array_index(headers, i);
1409 if (procheader_headername_equal(header->name,
1412 procheader_header_free(header);
1414 g_ptr_array_add(sorted_headers, header);
1416 g_ptr_array_remove_index(headers, i);
1422 if (prefs_common.show_other_header) {
1423 for (i = 0; i < headers->len; i++) {
1424 header = g_ptr_array_index(headers, i);
1425 g_ptr_array_add(sorted_headers, header);
1427 g_ptr_array_free(headers, TRUE);
1429 procheader_header_array_destroy(headers);
1432 return sorted_headers;
1435 static void textview_show_header(TextView *textview, GPtrArray *headers)
1437 GtkSText *text = GTK_STEXT(textview->text);
1441 g_return_if_fail(headers != NULL);
1443 gtk_stext_freeze(text);
1445 for (i = 0; i < headers->len; i++) {
1446 header = g_ptr_array_index(headers, i);
1447 g_return_if_fail(header->name != NULL);
1449 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1451 if (header->name[strlen(header->name) - 1] != ' ')
1452 gtk_stext_insert(text, textview->boldfont,
1453 NULL, NULL, " ", 1);
1455 if (procheader_headername_equal(header->name, "Subject") ||
1456 procheader_headername_equal(header->name, "From") ||
1457 procheader_headername_equal(header->name, "To") ||
1458 procheader_headername_equal(header->name, "Cc"))
1459 unfold_line(header->body);
1461 if (textview->text_is_mb == TRUE)
1462 conv_unreadable_locale(header->body);
1464 if (prefs_common.enable_color &&
1465 (procheader_headername_equal(header->name, "X-Mailer") ||
1466 procheader_headername_equal(header->name,
1468 strstr(header->body, "Sylpheed") != NULL)
1469 gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1471 else if (prefs_common.enable_color) {
1472 textview_make_clickable_parts(textview,
1473 NULL, NULL, &uri_color,
1476 textview_make_clickable_parts(textview,
1480 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1483 gtk_stext_thaw(text);
1486 gboolean textview_search_string(TextView *textview, const gchar *str,
1489 GtkSText *text = GTK_STEXT(textview->text);
1494 gboolean found = FALSE;
1496 g_return_val_if_fail(str != NULL, FALSE);
1498 wcs = strdup_mbstowcs(str);
1499 g_return_val_if_fail(wcs != NULL, FALSE);
1501 pos = textview->cur_pos;
1502 if (pos < textview->body_pos)
1503 pos = textview->body_pos;
1504 text_len = gtk_stext_get_length(text);
1505 if (text_len - pos < len) {
1510 for (; pos < text_len; pos++) {
1511 if (text_len - pos < len) break;
1512 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1514 gtk_widget_hide(GTK_WIDGET(textview->scrolledwin));
1515 gtk_editable_set_position(GTK_EDITABLE(text),
1517 gtk_editable_select_region(GTK_EDITABLE(text),
1519 gtk_widget_show(GTK_WIDGET(textview->scrolledwin));
1520 textview_set_position(textview, pos + len);
1524 if (text_len - pos == len) break;
1531 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1534 GtkSText *text = GTK_STEXT(textview->text);
1539 gboolean found = FALSE;
1541 g_return_val_if_fail(str != NULL, FALSE);
1543 wcs = strdup_mbstowcs(str);
1544 g_return_val_if_fail(wcs != NULL, FALSE);
1546 pos = textview->cur_pos;
1547 text_len = gtk_stext_get_length(text);
1548 if (text_len - textview->body_pos < len) {
1552 if (pos <= textview->body_pos || text_len - pos < len)
1553 pos = text_len - len;
1555 for (; pos >= textview->body_pos; pos--) {
1556 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1558 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1559 gtk_editable_select_region(GTK_EDITABLE(text),
1561 textview_set_position(textview, pos - 1);
1565 if (pos == textview->body_pos) break;
1572 void textview_scroll_one_line(TextView *textview, gboolean up)
1574 GtkSText *text = GTK_STEXT(textview->text);
1577 if (prefs_common.enable_smooth_scroll) {
1578 textview_smooth_scroll_one_line(textview, up);
1583 upper = text->vadj->upper - text->vadj->page_size;
1584 if (text->vadj->value < upper) {
1585 text->vadj->value +=
1586 text->vadj->step_increment * 4;
1588 MIN(text->vadj->value, upper);
1589 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1593 if (text->vadj->value > 0.0) {
1594 text->vadj->value -=
1595 text->vadj->step_increment * 4;
1597 MAX(text->vadj->value, 0.0);
1598 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1604 gboolean textview_scroll_page(TextView *textview, gboolean up)
1606 GtkSText *text = GTK_STEXT(textview->text);
1610 if (prefs_common.enable_smooth_scroll)
1611 return textview_smooth_scroll_page(textview, up);
1613 if (prefs_common.scroll_halfpage)
1614 page_incr = text->vadj->page_increment / 2;
1616 page_incr = text->vadj->page_increment;
1619 upper = text->vadj->upper - text->vadj->page_size;
1620 if (text->vadj->value < upper) {
1621 text->vadj->value += page_incr;
1622 text->vadj->value = MIN(text->vadj->value, upper);
1623 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1628 if (text->vadj->value > 0.0) {
1629 text->vadj->value -= page_incr;
1630 text->vadj->value = MAX(text->vadj->value, 0.0);
1631 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1640 static void textview_smooth_scroll_do(TextView *textview,
1641 gfloat old_value, gfloat last_value,
1644 GtkSText *text = GTK_STEXT(textview->text);
1649 if (old_value < last_value) {
1650 change_value = last_value - old_value;
1653 change_value = old_value - last_value;
1657 gdk_key_repeat_disable();
1659 for (i = step; i <= change_value; i += step) {
1660 text->vadj->value = old_value + (up ? -i : i);
1661 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1665 text->vadj->value = last_value;
1666 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1668 gdk_key_repeat_restore();
1671 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1673 GtkSText *text = GTK_STEXT(textview->text);
1679 upper = text->vadj->upper - text->vadj->page_size;
1680 if (text->vadj->value < upper) {
1681 old_value = text->vadj->value;
1682 last_value = text->vadj->value +
1683 text->vadj->step_increment * 4;
1684 last_value = MIN(last_value, upper);
1686 textview_smooth_scroll_do(textview, old_value,
1688 prefs_common.scroll_step);
1691 if (text->vadj->value > 0.0) {
1692 old_value = text->vadj->value;
1693 last_value = text->vadj->value -
1694 text->vadj->step_increment * 4;
1695 last_value = MAX(last_value, 0.0);
1697 textview_smooth_scroll_do(textview, old_value,
1699 prefs_common.scroll_step);
1704 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1706 GtkSText *text = GTK_STEXT(textview->text);
1712 if (prefs_common.scroll_halfpage)
1713 page_incr = text->vadj->page_increment / 2;
1715 page_incr = text->vadj->page_increment;
1718 upper = text->vadj->upper - text->vadj->page_size;
1719 if (text->vadj->value < upper) {
1720 old_value = text->vadj->value;
1721 last_value = text->vadj->value + page_incr;
1722 last_value = MIN(last_value, upper);
1724 textview_smooth_scroll_do(textview, old_value,
1726 prefs_common.scroll_step);
1730 if (text->vadj->value > 0.0) {
1731 old_value = text->vadj->value;
1732 last_value = text->vadj->value - page_incr;
1733 last_value = MAX(last_value, 0.0);
1735 textview_smooth_scroll_do(textview, old_value,
1737 prefs_common.scroll_step);
1745 #define KEY_PRESS_EVENT_STOP() \
1746 if (gtk_signal_n_emissions_by_name \
1747 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1748 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1749 "key_press_event"); \
1752 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1755 SummaryView *summaryview = NULL;
1756 MessageView *messageview = textview->messageview;
1758 if (!event) return FALSE;
1759 if (messageview->mainwin)
1760 summaryview = messageview->mainwin->summaryview;
1762 switch (event->keyval) {
1777 summary_pass_key_press_event(summaryview, event);
1779 textview_scroll_page(textview, FALSE);
1782 textview_scroll_page(textview, TRUE);
1785 textview_scroll_one_line(textview,
1786 (event->state & GDK_MOD1_MASK) != 0);
1790 summary_pass_key_press_event(summaryview, event);
1799 if (messageview->type == MVIEW_MIME &&
1800 textview == messageview->mimeview->textview) {
1801 KEY_PRESS_EVENT_STOP();
1802 mimeview_pass_key_press_event(messageview->mimeview,
1809 event->window != messageview->mainwin->window->window) {
1810 GdkEventKey tmpev = *event;
1812 tmpev.window = messageview->mainwin->window->window;
1813 KEY_PRESS_EVENT_STOP();
1814 gtk_widget_event(messageview->mainwin->window,
1815 (GdkEvent *)&tmpev);
1823 static gint show_url_timeout_cb(gpointer data)
1825 TextView *textview = (TextView *)data;
1827 if (textview->messageview->mainwin)
1828 if (textview->show_url_msgid)
1829 gtk_statusbar_remove(GTK_STATUSBAR(
1830 textview->messageview->mainwin->statusbar),
1831 textview->messageview->mainwin->folderview_cid,
1832 textview->show_url_msgid);
1836 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1840 textview->last_buttonpress = event->type;
1844 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1848 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1851 ((event->button == 1)
1852 || event->button == 2 || event->button == 3)) {
1855 /* double click seems to set the cursor after the current
1856 * word. The cursor position needs fixing, otherwise the
1857 * last word of a clickable zone will not work */
1858 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1859 textview->cur_pos--;
1862 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1863 RemoteURI *uri = (RemoteURI *)cur->data;
1865 if (textview->cur_pos >= uri->start &&
1866 textview->cur_pos <= uri->end) {
1867 /* single click: display url in statusbar */
1868 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1869 if (textview->messageview->mainwin) {
1870 if (textview->show_url_msgid) {
1871 gtk_timeout_remove(textview->show_url_timeout_tag);
1872 gtk_statusbar_remove(GTK_STATUSBAR(
1873 textview->messageview->mainwin->statusbar),
1874 textview->messageview->mainwin->folderview_cid,
1875 textview->show_url_msgid);
1876 textview->show_url_msgid = 0;
1878 textview->show_url_msgid = gtk_statusbar_push(
1879 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1880 textview->messageview->mainwin->folderview_cid,
1882 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1883 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1886 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1887 if (event->button == 3) {
1888 gchar *fromname, *fromaddress;
1891 fromaddress = g_strdup(uri->uri + 7);
1892 /* Hiroyuki: please put this function in utils.c! */
1893 fromname = procheader_get_fromname(fromaddress);
1894 extract_address(fromaddress);
1895 g_message("adding from textview %s <%s>", fromname, fromaddress);
1896 /* Add to address book - Match */
1897 addressbook_add_contact( fromname, fromaddress, NULL );
1899 g_free(fromaddress);
1902 PrefsAccount *account = NULL;
1903 FolderItem *folder_item;
1905 if (textview->messageview && textview->messageview->mainwin
1906 && textview->messageview->mainwin->summaryview
1907 && textview->messageview->mainwin->summaryview->folder_item) {
1908 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1909 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1910 account = account_find_from_id(folder_item->prefs->default_account);
1912 compose_new(account, uri->uri + 7, NULL);
1916 if (!g_strncasecmp(uri->uri, "pgp:", 4)) {
1917 GtkAdjustment *pos = gtk_scrolled_window_get_vadjustment(
1918 GTK_SCROLLED_WINDOW(textview->scrolledwin));
1919 gfloat vpos = pos->value;
1920 mimeview_check_signature(textview->messageview->mimeview);
1921 /* scroll back where we were */
1922 gtk_adjustment_set_value(pos, vpos);
1927 prefs_common.uri_cmd);
1933 textview->last_buttonpress = event->type;
1937 static void textview_uri_list_remove_all(GSList *uri_list)
1941 for (cur = uri_list; cur != NULL; cur = cur->next) {
1943 g_free(((RemoteURI *)cur->data)->uri);
1948 g_slist_free(uri_list);