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 (!mimeinfo->parent &&
477 mimeinfo->mime_type != MIME_TEXT &&
478 mimeinfo->mime_type != MIME_TEXT_HTML &&
479 mimeinfo->mime_type != MIME_TEXT_ENRICHED)
482 if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) {
487 if (mimeinfo->parent && mimeinfo->parent->boundary) {
488 boundary = mimeinfo->parent->boundary;
489 boundary_len = strlen(boundary);
492 while (fgets(buf, sizeof(buf), fp) != NULL)
493 if (buf[0] == '\r' || buf[0] == '\n') break;
495 if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
496 headers = textview_scan_header(textview, fp);
498 gtk_stext_freeze(text);
499 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
500 textview_show_header(textview, headers);
501 procheader_header_array_destroy(headers);
502 gtk_stext_thaw(text);
507 gtk_stext_freeze(text);
510 if (mimeinfo->sigstatus)
511 g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n",
512 mimeinfo->content_type, mimeinfo->sigstatus);
515 if (mimeinfo->filename || mimeinfo->name)
516 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
517 mimeinfo->filename ? mimeinfo->filename :
519 mimeinfo->content_type, mimeinfo->size);
521 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
522 mimeinfo->content_type, mimeinfo->size);
525 if (mimeinfo->sigstatus && !mimeinfo->sigstatus_full) {
527 /* use standard font */
528 gpointer oldfont = textview->msgfont;
529 textview->msgfont = NULL;
531 tmp = g_strconcat("pgp: ", _("Check signature"), NULL);
532 textview_write_link(textview, buf, tmp, NULL);
534 /* put things back */
535 textview->msgfont = (GdkFont *)oldfont;
538 } else if (mimeinfo->sigstatus) {
540 if (!strcmp(mimeinfo->sigstatus, _("Good signature")))
541 color = &good_sig_color;
542 else if (!strcmp(mimeinfo->sigstatus, _("BAD signature")))
543 color = &bad_sig_color;
545 color = &nocheck_sig_color;
546 gtk_stext_insert(text, NULL, color, NULL, buf, -1);
549 if (mimeinfo->mime_type != MIME_TEXT &&
550 mimeinfo->mime_type != MIME_TEXT_HTML &&
551 mimeinfo->mime_type != MIME_TEXT_ENRICHED) {
552 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
554 if (!mimeinfo->main &&
556 mimeinfo->parent->children != mimeinfo)
557 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
558 else if (prefs_common.display_header)
559 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
560 if (textview->messageview->forced_charset)
561 charset = textview->messageview->forced_charset;
562 else if (prefs_common.force_charset)
563 charset = prefs_common.force_charset;
564 else if (mimeinfo->charset)
565 charset = mimeinfo->charset;
566 textview_write_body(textview, mimeinfo, fp, charset);
569 gtk_stext_thaw(text);
572 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
576 g_return_if_fail(mimeinfo != NULL);
577 g_return_if_fail(fp != NULL);
579 level = mimeinfo->level;
582 textview_add_part(textview, mimeinfo, fp);
583 if (mimeinfo->parent && mimeinfo->parent->content_type &&
584 !strcasecmp(mimeinfo->parent->content_type,
585 "multipart/alternative"))
586 mimeinfo = mimeinfo->parent->next;
588 mimeinfo = procmime_mimeinfo_next(mimeinfo);
589 if (!mimeinfo || mimeinfo->level <= level)
594 #define TEXT_INSERT(str) \
595 gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
597 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
601 if (!partinfo) return;
603 textview_set_font(textview, NULL);
604 text = GTK_STEXT(textview->text);
605 textview_clear(textview);
607 gtk_stext_freeze(text);
609 TEXT_INSERT(_("To save this part, pop up the context menu with "));
610 TEXT_INSERT(_("right click and select `Save as...', "));
611 TEXT_INSERT(_("or press `y' key.\n\n"));
613 TEXT_INSERT(_("To display this part as a text message, select "));
614 TEXT_INSERT(_("`Display as text', or press `t' key.\n\n"));
616 TEXT_INSERT(_("To open this part with external program, select "));
617 TEXT_INSERT(_("`Open' or `Open with...', "));
618 TEXT_INSERT(_("or double-click, or click the center button, "));
619 TEXT_INSERT(_("or press `l' key."));
621 gtk_stext_thaw(text);
625 void textview_show_signature_part(TextView *textview, MimeInfo *partinfo)
629 if (!partinfo) return;
631 textview_set_font(textview, NULL);
632 text = GTK_STEXT(textview->text);
633 textview_clear(textview);
635 gtk_stext_freeze(text);
637 if (partinfo->sigstatus_full == NULL) {
638 TEXT_INSERT(_("This signature has not been checked yet.\n"));
639 TEXT_INSERT(_("To check it, pop up the context menu with\n"));
640 TEXT_INSERT(_("right click and select `Check signature'.\n"));
642 TEXT_INSERT(partinfo->sigstatus_full);
645 gtk_stext_thaw(text);
647 #endif /* USE_GPGME */
651 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
652 FILE *fp, const gchar *charset)
658 conv = conv_code_converter_new(charset);
660 tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
662 textview->is_in_signature = FALSE;
665 if (mimeinfo->mime_type == MIME_TEXT_HTML)
666 textview_show_html(textview, tmpfp, conv);
667 else if (mimeinfo->mime_type == MIME_TEXT_ENRICHED)
668 textview_show_ertf(textview, tmpfp, conv);
670 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
671 textview_write_line(textview, buf, conv);
675 conv_code_converter_destroy(conv);
678 static void textview_show_html(TextView *textview, FILE *fp,
684 parser = html_parser_new(fp, conv);
685 g_return_if_fail(parser != NULL);
687 while ((str = html_parse(parser)) != NULL) {
688 if (parser->state == HTML_HREF) {
689 /* first time : get and copy the URL */
690 if (parser->href == NULL) {
691 /* ALF - the sylpheed html parser returns an empty string,
692 * if still inside an <a>, but already parsed past HREF */
693 str = strtok(str, " ");
695 parser->href = strdup(str);
696 /* the URL may (or not) be followed by the
698 str = strtok(NULL, "");
702 textview_write_link(textview, str, parser->href, NULL);
704 textview_write_line(textview, str, NULL);
706 html_parser_destroy(parser);
709 static void textview_show_ertf(TextView *textview, FILE *fp,
715 parser = ertf_parser_new(fp, conv);
716 g_return_if_fail(parser != NULL);
718 while ((str = ertf_parse(parser)) != NULL) {
719 textview_write_line(textview, str, NULL);
722 ertf_parser_destroy(parser);
725 /* get_uri_part() - retrieves a URI starting from scanpos.
726 Returns TRUE if succesful */
727 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
728 const gchar **bp, const gchar **ep)
732 g_return_val_if_fail(start != NULL, FALSE);
733 g_return_val_if_fail(scanpos != NULL, FALSE);
734 g_return_val_if_fail(bp != NULL, FALSE);
735 g_return_val_if_fail(ep != NULL, FALSE);
739 /* find end point of URI */
740 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
741 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
745 /* no punctuation at end of string */
747 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
748 * should pass some URI type to this function and decide on that whether
749 * to perform punctuation stripping */
751 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
753 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
763 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
765 return g_strndup(bp, ep - bp);
768 /* valid mail address characters */
769 #define IS_RFC822_CHAR(ch) \
774 !strchr("(),;<>\"", (ch)))
776 /* alphabet and number within 7bit ASCII */
777 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
778 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
780 static GHashTable *create_domain_tab(void)
782 static const gchar *toplvl_domains [] = {
784 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
785 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
786 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
787 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
788 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
789 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
790 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
791 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
792 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
793 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
794 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
795 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
796 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
797 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
798 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
799 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
800 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
801 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
802 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
803 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
804 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
805 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
806 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
807 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
808 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
809 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
812 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
814 g_return_val_if_fail(htab, NULL);
815 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
816 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
820 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
822 const gint MAX_LVL_DOM_NAME_LEN = 6;
823 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
824 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
827 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
830 for (p = buf; p < m && first < last; *p++ = *first++)
834 return g_hash_table_lookup(tab, buf) != NULL;
837 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
838 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
839 const gchar **bp, const gchar **ep)
841 /* more complex than the uri part because we need to scan back and forward starting from
842 * the scan position. */
843 gboolean result = FALSE;
844 const gchar *bp_ = NULL;
845 const gchar *ep_ = NULL;
846 static GHashTable *dom_tab;
847 const gchar *last_dot = NULL;
848 const gchar *prelast_dot = NULL;
849 const gchar *last_tld_char = NULL;
851 /* the informative part of the email address (describing the name
852 * of the email address owner) may contain quoted parts. the
853 * closure stack stores the last encountered quotes. */
854 gchar closure_stack[128];
855 gchar *ptr = closure_stack;
857 g_return_val_if_fail(start != NULL, FALSE);
858 g_return_val_if_fail(scanpos != NULL, FALSE);
859 g_return_val_if_fail(bp != NULL, FALSE);
860 g_return_val_if_fail(ep != NULL, FALSE);
863 dom_tab = create_domain_tab();
864 g_return_val_if_fail(dom_tab, FALSE);
866 /* scan start of address */
867 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
870 /* TODO: should start with an alnum? */
872 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
875 if (bp_ != scanpos) {
876 /* scan end of address */
877 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
879 prelast_dot = last_dot;
881 if (*(last_dot + 1) == '.') {
882 if (prelast_dot == NULL)
884 last_dot = prelast_dot;
889 /* TODO: really should terminate with an alnum? */
890 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
894 if (last_dot == NULL)
897 last_dot = prelast_dot;
898 if (last_dot == NULL || (scanpos + 1 >= last_dot))
902 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
903 if (*last_tld_char == '?')
906 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
913 if (!result) return FALSE;
915 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
916 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
919 /* see if this is <bracketed>; in this case we also scan for the informative part. */
920 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
923 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
924 #define IN_STACK() (ptr > closure_stack)
925 /* has underrun check */
926 #define POP_STACK() if(IN_STACK()) --ptr
927 /* has overrun check */
928 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
929 /* has underrun check */
930 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
934 /* scan for the informative part. */
935 for (bp_ -= 2; bp_ >= start; bp_--) {
936 /* if closure on the stack keep scanning */
937 if (PEEK_STACK() == *bp_) {
941 if (*bp_ == '\'' || *bp_ == '"') {
946 /* if nothing in the closure stack, do the special conditions
947 * the following if..else expression simply checks whether
948 * a token is acceptable. if not acceptable, the clause
949 * should terminate the loop with a 'break' */
952 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
953 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
954 /* hyphens are allowed, but only in
956 } else if (!ispunct(*bp_)) {
957 /* but anything not being a punctiation
960 break; /* anything else is rejected */
973 /* scan forward (should start with an alnum) */
974 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
984 #undef IS_RFC822_CHAR
986 static gchar *make_email_string(const gchar *bp, const gchar *ep)
988 /* returns a mailto: URI; mailto: is also used to detect the
989 * uri type later on in the button_pressed signal handler */
993 tmp = g_strndup(bp, ep - bp);
994 result = g_strconcat("mailto:", tmp, NULL);
1000 #define ADD_TXT_POS(bp_, ep_, pti_) \
1001 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1002 last = last->next; \
1003 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1004 last->next = NULL; \
1006 g_warning("alloc error scanning URIs\n"); \
1007 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
1012 /* textview_make_clickable_parts() - colorizes clickable parts */
1013 static void textview_make_clickable_parts(TextView *textview,
1016 GdkColor *uri_color,
1017 const gchar *linebuf)
1019 /* parse table - in order of priority */
1021 const gchar *needle; /* token */
1023 /* token search function */
1024 gchar *(*search) (const gchar *haystack,
1025 const gchar *needle);
1026 /* part parsing function */
1027 gboolean (*parse) (const gchar *start,
1028 const gchar *scanpos,
1031 /* part to URI function */
1032 gchar *(*build_uri) (const gchar *bp,
1036 static struct table parser[] = {
1037 {"http://", strcasestr, get_uri_part, make_uri_string},
1038 {"https://", strcasestr, get_uri_part, make_uri_string},
1039 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1040 {"www.", strcasestr, get_uri_part, make_uri_string},
1041 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1042 {"@", strcasestr, get_email_part, make_email_string}
1044 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1047 const gchar *walk, *bp, *ep;
1050 const gchar *bp, *ep; /* text position */
1051 gint pti; /* index in parse table */
1052 struct txtpos *next; /* next */
1053 } head = {NULL, NULL, 0, NULL}, *last = &head;
1055 GtkSText *text = GTK_STEXT(textview->text);
1057 /* parse for clickable parts, and build a list of begin and end positions */
1058 for (walk = linebuf, n = 0;;) {
1059 gint last_index = PARSE_ELEMS;
1060 gchar *scanpos = NULL;
1062 /* FIXME: this looks phony. scanning for anything in the parse table */
1063 for (n = 0; n < PARSE_ELEMS; n++) {
1066 tmp = parser[n].search(walk, parser[n].needle);
1068 if (scanpos == NULL || tmp < scanpos) {
1076 /* check if URI can be parsed */
1077 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1078 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1079 ADD_TXT_POS(bp, ep, last_index);
1083 strlen(parser[last_index].needle);
1088 /* colorize this line */
1090 const gchar *normal_text = linebuf;
1093 for (last = head.next; last != NULL;
1094 normal_text = last->ep, last = last->next) {
1097 uri = g_new(RemoteURI, 1);
1098 if (last->bp - normal_text > 0)
1099 gtk_stext_insert(text, font,
1102 last->bp - normal_text);
1103 uri->uri = parser[last->pti].build_uri(last->bp,
1105 uri->start = gtk_stext_get_point(text);
1106 gtk_stext_insert(text, font, uri_color,
1107 NULL, last->bp, last->ep - last->bp);
1108 uri->end = gtk_stext_get_point(text);
1109 textview->uri_list =
1110 g_slist_append(textview->uri_list, uri);
1114 gtk_stext_insert(text, font, fg_color,
1115 NULL, normal_text, -1);
1117 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1122 static void textview_write_line(TextView *textview, const gchar *str,
1123 CodeConverter *conv)
1125 GtkSText *text = GTK_STEXT(textview->text);
1126 gchar buf[BUFFSIZE];
1128 gint quotelevel = -1;
1131 if (textview->text_is_mb)
1132 conv_localetodisp(buf, sizeof(buf), str);
1134 strncpy2(buf, str, sizeof(buf));
1135 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1136 conv_localetodisp(buf, sizeof(buf), str);
1137 else if (textview->text_is_mb)
1138 conv_unreadable_locale(buf);
1141 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1144 /* change color of quotation
1145 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1146 Up to 3 levels of quotations are detected, and each
1147 level is colored using a different color. */
1148 if (prefs_common.enable_color
1149 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1150 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1152 /* set up the correct foreground color */
1153 if (quotelevel > 2) {
1154 /* recycle colors */
1155 if (prefs_common.recycle_quote_colors)
1162 if (quotelevel == -1)
1165 fg_color = "e_colors[quotelevel];
1167 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1168 fg_color = &signature_color;
1169 textview->is_in_signature = TRUE;
1172 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1173 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1175 if (prefs_common.enable_color)
1176 textview_make_clickable_parts(textview, textview->msgfont,
1177 fg_color, &uri_color, buf);
1179 textview_make_clickable_parts(textview, textview->msgfont,
1180 fg_color, NULL, buf);
1183 void textview_write_link(TextView *textview, const gchar *str,
1184 const gchar *uri, CodeConverter *conv)
1186 GdkColor *link_color = NULL;
1187 GtkSText *text = GTK_STEXT(textview->text);
1188 gchar buf[BUFFSIZE];
1196 if (textview->text_is_mb)
1197 conv_localetodisp(buf, sizeof(buf), str);
1199 strncpy2(buf, str, sizeof(buf));
1200 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1201 conv_localetodisp(buf, sizeof(buf), str);
1202 else if (textview->text_is_mb)
1203 conv_unreadable_locale(buf);
1207 for (bufp = buf; isspace(*bufp); bufp++)
1208 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1210 if (prefs_common.enable_color) {
1211 link_color = &uri_color;
1213 r_uri = g_new(RemoteURI, 1);
1214 r_uri->uri = g_strdup(uri);
1215 r_uri->start = gtk_stext_get_point(text);
1216 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1217 r_uri->end = gtk_stext_get_point(text);
1218 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1221 void textview_clear(TextView *textview)
1223 GtkSText *text = GTK_STEXT(textview->text);
1225 gtk_stext_freeze(text);
1226 gtk_stext_set_point(text, 0);
1227 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1228 gtk_stext_thaw(text);
1230 textview_uri_list_remove_all(textview->uri_list);
1231 textview->uri_list = NULL;
1233 textview->body_pos = 0;
1234 textview->cur_pos = 0;
1237 void textview_destroy(TextView *textview)
1239 textview_uri_list_remove_all(textview->uri_list);
1240 textview->uri_list = NULL;
1242 if (!textview->scrolledwin_sb->parent)
1243 gtk_widget_destroy(textview->scrolledwin_sb);
1244 if (!textview->scrolledwin_mb->parent)
1245 gtk_widget_destroy(textview->scrolledwin_mb);
1247 if (textview->msgfont)
1248 gdk_font_unref(textview->msgfont);
1249 if (textview->boldfont)
1250 gdk_font_unref(textview->boldfont);
1255 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1257 textview->show_all_headers = all_headers;
1260 void textview_set_font(TextView *textview, const gchar *codeset)
1262 gboolean use_fontset = TRUE;
1264 /* In multi-byte mode, GtkSText can't display 8bit characters
1265 correctly, so it must be single-byte mode. */
1266 if (MB_CUR_MAX > 1) {
1267 if (codeset && conv_get_current_charset() != C_UTF_8) {
1268 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1269 !g_strcasecmp(codeset, "BALTIC"))
1270 use_fontset = FALSE;
1271 else if (conv_get_current_charset() != C_EUC_JP &&
1272 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1273 !g_strncasecmp(codeset, "CP", 2) ||
1274 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1275 use_fontset = FALSE;
1278 use_fontset = FALSE;
1280 if (textview->text_is_mb && !use_fontset) {
1283 parent = textview->scrolledwin_mb->parent;
1284 gtkut_container_remove(GTK_CONTAINER(parent),
1285 textview->scrolledwin_mb);
1286 gtk_container_add(GTK_CONTAINER(parent),
1287 textview->scrolledwin_sb);
1289 textview->text = textview->text_sb;
1290 textview->text_is_mb = FALSE;
1291 } else if (!textview->text_is_mb && use_fontset) {
1294 parent = textview->scrolledwin_sb->parent;
1295 gtkut_container_remove(GTK_CONTAINER(parent),
1296 textview->scrolledwin_sb);
1297 gtk_container_add(GTK_CONTAINER(parent),
1298 textview->scrolledwin_mb);
1300 textview->text = textview->text_mb;
1301 textview->text_is_mb = TRUE;
1304 if (prefs_common.textfont) {
1309 text_mb_font->ascent = text_mb_font_orig_ascent;
1310 text_mb_font->descent = text_mb_font_orig_descent;
1312 font = gdk_fontset_load(prefs_common.textfont);
1313 if (font && text_mb_font != font) {
1315 gdk_font_unref(text_mb_font);
1316 text_mb_font = font;
1317 text_mb_font_orig_ascent = font->ascent;
1318 text_mb_font_orig_descent = font->descent;
1322 text_sb_font->ascent = text_sb_font_orig_ascent;
1323 text_sb_font->descent = text_sb_font_orig_descent;
1326 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1328 font = gtkut_font_load_from_fontset
1329 (prefs_common.textfont);
1330 if (font && text_sb_font != font) {
1332 gdk_font_unref(text_sb_font);
1333 text_sb_font = font;
1334 text_sb_font_orig_ascent = font->ascent;
1335 text_sb_font_orig_descent = font->descent;
1340 gint ascent, descent;
1342 descent = prefs_common.line_space / 2;
1343 ascent = prefs_common.line_space - descent;
1344 font->ascent += ascent;
1345 font->descent += descent;
1347 if (textview->msgfont)
1348 gdk_font_unref(textview->msgfont);
1349 textview->msgfont = font;
1354 if (!textview->boldfont && prefs_common.boldfont)
1355 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1357 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1373 H_ORGANIZATION = 11,
1376 void textview_set_position(TextView *textview, gint pos)
1380 gtk_stext_get_length(GTK_STEXT(textview->text));
1382 textview->cur_pos = pos;
1386 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1388 gchar buf[BUFFSIZE];
1389 GPtrArray *headers, *sorted_headers;
1390 GSList *disphdr_list;
1394 g_return_val_if_fail(fp != NULL, NULL);
1396 if (textview->show_all_headers)
1397 return procheader_get_header_array_asis(fp);
1399 if (!prefs_common.display_header) {
1400 while (fgets(buf, sizeof(buf), fp) != NULL)
1401 if (buf[0] == '\r' || buf[0] == '\n') break;
1405 headers = procheader_get_header_array_asis(fp);
1407 sorted_headers = g_ptr_array_new();
1409 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1410 disphdr_list = disphdr_list->next) {
1411 DisplayHeaderProp *dp =
1412 (DisplayHeaderProp *)disphdr_list->data;
1414 for (i = 0; i < headers->len; i++) {
1415 header = g_ptr_array_index(headers, i);
1417 if (procheader_headername_equal(header->name,
1420 procheader_header_free(header);
1422 g_ptr_array_add(sorted_headers, header);
1424 g_ptr_array_remove_index(headers, i);
1430 if (prefs_common.show_other_header) {
1431 for (i = 0; i < headers->len; i++) {
1432 header = g_ptr_array_index(headers, i);
1433 g_ptr_array_add(sorted_headers, header);
1435 g_ptr_array_free(headers, TRUE);
1437 procheader_header_array_destroy(headers);
1440 return sorted_headers;
1443 static void textview_show_header(TextView *textview, GPtrArray *headers)
1445 GtkSText *text = GTK_STEXT(textview->text);
1449 g_return_if_fail(headers != NULL);
1451 gtk_stext_freeze(text);
1453 for (i = 0; i < headers->len; i++) {
1454 header = g_ptr_array_index(headers, i);
1455 g_return_if_fail(header->name != NULL);
1457 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1459 if (header->name[strlen(header->name) - 1] != ' ')
1460 gtk_stext_insert(text, textview->boldfont,
1461 NULL, NULL, " ", 1);
1463 if (procheader_headername_equal(header->name, "Subject") ||
1464 procheader_headername_equal(header->name, "From") ||
1465 procheader_headername_equal(header->name, "To") ||
1466 procheader_headername_equal(header->name, "Cc"))
1467 unfold_line(header->body);
1469 if (textview->text_is_mb == TRUE)
1470 conv_unreadable_locale(header->body);
1472 if (prefs_common.enable_color &&
1473 (procheader_headername_equal(header->name, "X-Mailer") ||
1474 procheader_headername_equal(header->name,
1476 strstr(header->body, "Sylpheed") != NULL)
1477 gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1479 else if (prefs_common.enable_color) {
1480 textview_make_clickable_parts(textview,
1481 NULL, NULL, &uri_color,
1484 textview_make_clickable_parts(textview,
1488 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1491 gtk_stext_thaw(text);
1494 gboolean textview_search_string(TextView *textview, const gchar *str,
1497 GtkSText *text = GTK_STEXT(textview->text);
1502 gboolean found = FALSE;
1504 g_return_val_if_fail(str != NULL, FALSE);
1506 wcs = strdup_mbstowcs(str);
1507 g_return_val_if_fail(wcs != NULL, FALSE);
1509 pos = textview->cur_pos;
1510 if (pos < textview->body_pos)
1511 pos = textview->body_pos;
1512 text_len = gtk_stext_get_length(text);
1513 if (text_len - pos < len) {
1518 for (; pos < text_len; pos++) {
1519 if (text_len - pos < len) break;
1520 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1522 gtk_widget_hide(GTK_WIDGET(textview->scrolledwin));
1523 gtk_editable_set_position(GTK_EDITABLE(text),
1525 gtk_editable_select_region(GTK_EDITABLE(text),
1527 gtk_widget_show(GTK_WIDGET(textview->scrolledwin));
1528 textview_set_position(textview, pos + len);
1532 if (text_len - pos == len) break;
1539 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1542 GtkSText *text = GTK_STEXT(textview->text);
1547 gboolean found = FALSE;
1549 g_return_val_if_fail(str != NULL, FALSE);
1551 wcs = strdup_mbstowcs(str);
1552 g_return_val_if_fail(wcs != NULL, FALSE);
1554 pos = textview->cur_pos;
1555 text_len = gtk_stext_get_length(text);
1556 if (text_len - textview->body_pos < len) {
1560 if (pos <= textview->body_pos || text_len - pos < len)
1561 pos = text_len - len;
1563 for (; pos >= textview->body_pos; pos--) {
1564 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1566 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1567 gtk_editable_select_region(GTK_EDITABLE(text),
1569 textview_set_position(textview, pos - 1);
1573 if (pos == textview->body_pos) break;
1580 void textview_scroll_one_line(TextView *textview, gboolean up)
1582 GtkSText *text = GTK_STEXT(textview->text);
1585 if (prefs_common.enable_smooth_scroll) {
1586 textview_smooth_scroll_one_line(textview, up);
1591 upper = text->vadj->upper - text->vadj->page_size;
1592 if (text->vadj->value < upper) {
1593 text->vadj->value +=
1594 text->vadj->step_increment * 4;
1596 MIN(text->vadj->value, upper);
1597 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1601 if (text->vadj->value > 0.0) {
1602 text->vadj->value -=
1603 text->vadj->step_increment * 4;
1605 MAX(text->vadj->value, 0.0);
1606 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1612 gboolean textview_scroll_page(TextView *textview, gboolean up)
1614 GtkSText *text = GTK_STEXT(textview->text);
1618 if (prefs_common.enable_smooth_scroll)
1619 return textview_smooth_scroll_page(textview, up);
1621 if (prefs_common.scroll_halfpage)
1622 page_incr = text->vadj->page_increment / 2;
1624 page_incr = text->vadj->page_increment;
1627 upper = text->vadj->upper - text->vadj->page_size;
1628 if (text->vadj->value < upper) {
1629 text->vadj->value += page_incr;
1630 text->vadj->value = MIN(text->vadj->value, upper);
1631 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1636 if (text->vadj->value > 0.0) {
1637 text->vadj->value -= page_incr;
1638 text->vadj->value = MAX(text->vadj->value, 0.0);
1639 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1648 static void textview_smooth_scroll_do(TextView *textview,
1649 gfloat old_value, gfloat last_value,
1652 GtkSText *text = GTK_STEXT(textview->text);
1657 if (old_value < last_value) {
1658 change_value = last_value - old_value;
1661 change_value = old_value - last_value;
1665 gdk_key_repeat_disable();
1667 for (i = step; i <= change_value; i += step) {
1668 text->vadj->value = old_value + (up ? -i : i);
1669 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1673 text->vadj->value = last_value;
1674 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1676 gdk_key_repeat_restore();
1679 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1681 GtkSText *text = GTK_STEXT(textview->text);
1687 upper = text->vadj->upper - text->vadj->page_size;
1688 if (text->vadj->value < upper) {
1689 old_value = text->vadj->value;
1690 last_value = text->vadj->value +
1691 text->vadj->step_increment * 4;
1692 last_value = MIN(last_value, upper);
1694 textview_smooth_scroll_do(textview, old_value,
1696 prefs_common.scroll_step);
1699 if (text->vadj->value > 0.0) {
1700 old_value = text->vadj->value;
1701 last_value = text->vadj->value -
1702 text->vadj->step_increment * 4;
1703 last_value = MAX(last_value, 0.0);
1705 textview_smooth_scroll_do(textview, old_value,
1707 prefs_common.scroll_step);
1712 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1714 GtkSText *text = GTK_STEXT(textview->text);
1720 if (prefs_common.scroll_halfpage)
1721 page_incr = text->vadj->page_increment / 2;
1723 page_incr = text->vadj->page_increment;
1726 upper = text->vadj->upper - text->vadj->page_size;
1727 if (text->vadj->value < upper) {
1728 old_value = text->vadj->value;
1729 last_value = text->vadj->value + page_incr;
1730 last_value = MIN(last_value, upper);
1732 textview_smooth_scroll_do(textview, old_value,
1734 prefs_common.scroll_step);
1738 if (text->vadj->value > 0.0) {
1739 old_value = text->vadj->value;
1740 last_value = text->vadj->value - page_incr;
1741 last_value = MAX(last_value, 0.0);
1743 textview_smooth_scroll_do(textview, old_value,
1745 prefs_common.scroll_step);
1753 #define KEY_PRESS_EVENT_STOP() \
1754 if (gtk_signal_n_emissions_by_name \
1755 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1756 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1757 "key_press_event"); \
1760 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1763 SummaryView *summaryview = NULL;
1764 MessageView *messageview = textview->messageview;
1766 if (!event) return FALSE;
1767 if (messageview->mainwin)
1768 summaryview = messageview->mainwin->summaryview;
1770 switch (event->keyval) {
1785 summary_pass_key_press_event(summaryview, event);
1787 textview_scroll_page(textview, FALSE);
1790 textview_scroll_page(textview, TRUE);
1793 textview_scroll_one_line(textview,
1794 (event->state & GDK_MOD1_MASK) != 0);
1798 summary_pass_key_press_event(summaryview, event);
1807 if (messageview->type == MVIEW_MIME &&
1808 textview == messageview->mimeview->textview) {
1809 KEY_PRESS_EVENT_STOP();
1810 mimeview_pass_key_press_event(messageview->mimeview,
1817 event->window != messageview->mainwin->window->window) {
1818 GdkEventKey tmpev = *event;
1820 tmpev.window = messageview->mainwin->window->window;
1821 KEY_PRESS_EVENT_STOP();
1822 gtk_widget_event(messageview->mainwin->window,
1823 (GdkEvent *)&tmpev);
1831 static gint show_url_timeout_cb(gpointer data)
1833 TextView *textview = (TextView *)data;
1835 if (textview->messageview->mainwin)
1836 if (textview->show_url_msgid)
1837 gtk_statusbar_remove(GTK_STATUSBAR(
1838 textview->messageview->mainwin->statusbar),
1839 textview->messageview->mainwin->folderview_cid,
1840 textview->show_url_msgid);
1844 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1848 textview->last_buttonpress = event->type;
1852 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1856 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1859 ((event->button == 1)
1860 || event->button == 2 || event->button == 3)) {
1863 /* double click seems to set the cursor after the current
1864 * word. The cursor position needs fixing, otherwise the
1865 * last word of a clickable zone will not work */
1866 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1867 textview->cur_pos--;
1870 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1871 RemoteURI *uri = (RemoteURI *)cur->data;
1873 if (textview->cur_pos >= uri->start &&
1874 textview->cur_pos <= uri->end) {
1875 /* single click: display url in statusbar */
1876 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1877 if (textview->messageview->mainwin) {
1878 if (textview->show_url_msgid) {
1879 gtk_timeout_remove(textview->show_url_timeout_tag);
1880 gtk_statusbar_remove(GTK_STATUSBAR(
1881 textview->messageview->mainwin->statusbar),
1882 textview->messageview->mainwin->folderview_cid,
1883 textview->show_url_msgid);
1884 textview->show_url_msgid = 0;
1886 textview->show_url_msgid = gtk_statusbar_push(
1887 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1888 textview->messageview->mainwin->folderview_cid,
1890 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1891 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1894 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1895 if (event->button == 3) {
1896 gchar *fromname, *fromaddress;
1899 fromaddress = g_strdup(uri->uri + 7);
1900 /* Hiroyuki: please put this function in utils.c! */
1901 fromname = procheader_get_fromname(fromaddress);
1902 extract_address(fromaddress);
1903 g_message("adding from textview %s <%s>", fromname, fromaddress);
1904 /* Add to address book - Match */
1905 addressbook_add_contact( fromname, fromaddress, NULL );
1907 g_free(fromaddress);
1910 PrefsAccount *account = NULL;
1911 FolderItem *folder_item;
1913 if (textview->messageview && textview->messageview->mainwin
1914 && textview->messageview->mainwin->summaryview
1915 && textview->messageview->mainwin->summaryview->folder_item) {
1916 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1917 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1918 account = account_find_from_id(folder_item->prefs->default_account);
1920 compose_new(account, uri->uri + 7, NULL);
1924 if (!g_strncasecmp(uri->uri, "pgp:", 4)) {
1925 GtkAdjustment *pos = gtk_scrolled_window_get_vadjustment(
1926 GTK_SCROLLED_WINDOW(textview->scrolledwin));
1927 gfloat vpos = pos->value;
1928 mimeview_check_signature(textview->messageview->mimeview);
1929 /* scroll back where we were */
1930 gtk_adjustment_set_value(pos, vpos);
1935 prefs_common.uri_cmd);
1941 textview->last_buttonpress = event->type;
1945 static void textview_uri_list_remove_all(GSList *uri_list)
1949 for (cur = uri_list; cur != NULL; cur = cur->next) {
1951 g_free(((RemoteURI *)cur->data)->uri);
1956 g_slist_free(uri_list);