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, tmp, buf, NULL);
528 /* put things back */
529 textview->msgfont = (GdkFont *)oldfont;
532 } else if (mimeinfo->sigstatus) {
534 if (!strcmp(mimeinfo->sigstatus, _("Good signature")))
535 color = &good_sig_color;
536 else if (!strcmp(mimeinfo->sigstatus, _("BAD signature")))
537 color = &bad_sig_color;
539 color = &nocheck_sig_color;
540 gtk_stext_insert(text, NULL, color, NULL, buf, -1);
543 if (mimeinfo->mime_type != MIME_TEXT &&
544 mimeinfo->mime_type != MIME_TEXT_HTML &&
545 mimeinfo->mime_type != MIME_TEXT_ENRICHED) {
546 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
548 if (!mimeinfo->main &&
550 mimeinfo->parent->children != mimeinfo)
551 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
552 else if (prefs_common.display_header)
553 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
554 if (prefs_common.force_charset)
555 charset = prefs_common.force_charset;
556 else if (mimeinfo->charset)
557 charset = mimeinfo->charset;
558 textview_write_body(textview, mimeinfo, fp, charset);
561 gtk_stext_thaw(text);
564 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
568 g_return_if_fail(mimeinfo != NULL);
569 g_return_if_fail(fp != NULL);
571 level = mimeinfo->level;
574 textview_add_part(textview, mimeinfo, fp);
575 if (mimeinfo->parent && mimeinfo->parent->content_type &&
576 !strcasecmp(mimeinfo->parent->content_type,
577 "multipart/alternative"))
578 mimeinfo = mimeinfo->parent->next;
580 mimeinfo = procmime_mimeinfo_next(mimeinfo);
581 if (!mimeinfo || mimeinfo->level <= level)
586 #define TEXT_INSERT(str) \
587 gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
589 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
593 if (!partinfo) return;
595 textview_set_font(textview, NULL);
596 text = GTK_STEXT(textview->text);
597 textview_clear(textview);
599 gtk_stext_freeze(text);
601 TEXT_INSERT(_("To save this part, pop up the context menu with "));
602 TEXT_INSERT(_("right click and select `Save as...', "));
603 TEXT_INSERT(_("or press `y' key.\n\n"));
605 TEXT_INSERT(_("To display this part as a text message, select "));
606 TEXT_INSERT(_("`Display as text', or press `t' key.\n\n"));
608 TEXT_INSERT(_("To display this part as an image, select "));
609 TEXT_INSERT(_("`Display image', or press `i' key.\n\n"));
611 TEXT_INSERT(_("To open this part with external program, select "));
612 TEXT_INSERT(_("`Open' or `Open with...', "));
613 TEXT_INSERT(_("or double-click, or click the center button, "));
614 TEXT_INSERT(_("or press `l' key."));
616 gtk_stext_thaw(text);
620 void textview_show_signature_part(TextView *textview, MimeInfo *partinfo)
624 if (!partinfo) return;
626 textview_set_font(textview, NULL);
627 text = GTK_STEXT(textview->text);
628 textview_clear(textview);
630 gtk_stext_freeze(text);
632 if (partinfo->sigstatus_full == NULL) {
633 TEXT_INSERT(_("This signature has not been checked yet.\n"));
634 TEXT_INSERT(_("To check it, pop up the context menu with\n"));
635 TEXT_INSERT(_("right click and select `Check signature'.\n"));
637 TEXT_INSERT(partinfo->sigstatus_full);
640 gtk_stext_thaw(text);
642 #endif /* USE_GPGME */
646 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
647 FILE *fp, const gchar *charset)
653 conv = conv_code_converter_new(charset);
655 tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
657 textview->is_in_signature = FALSE;
661 if (mimeinfo->mime_type == MIME_TEXT_HTML)
662 textview_show_html(textview, tmpfp, conv);
663 else if (mimeinfo->mime_type == MIME_TEXT_ENRICHED)
664 textview_show_ertf(textview, tmpfp, conv);
666 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
667 textview_write_line(textview, buf, conv);
671 conv_code_converter_destroy(conv);
674 static void textview_show_html(TextView *textview, FILE *fp,
681 parser = html_parser_new(fp, conv);
682 g_return_if_fail(parser != NULL);
684 while ((str = html_parse(parser)) != NULL) {
685 if (parser->state == HTML_HREF) {
686 /* first time : get and copy the URL */
688 /* ALF - the sylpheed html parser returns an empty string,
689 * if still inside an <a>, but already parsed past HREF */
690 str = strtok(str, " ");
693 /* the URL may (or not) be followed by the
695 str = strtok(NULL, "");
699 textview_write_link(textview, url, str, NULL);
706 textview_write_line(textview, str, NULL);
710 html_parser_destroy(parser);
713 static void textview_show_ertf(TextView *textview, FILE *fp,
719 parser = ertf_parser_new(fp, conv);
720 g_return_if_fail(parser != NULL);
722 while ((str = ertf_parse(parser)) != NULL) {
723 textview_write_line(textview, str, NULL);
726 ertf_parser_destroy(parser);
729 /* get_uri_part() - retrieves a URI starting from scanpos.
730 Returns TRUE if succesful */
731 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
732 const gchar **bp, const gchar **ep)
736 g_return_val_if_fail(start != NULL, FALSE);
737 g_return_val_if_fail(scanpos != NULL, FALSE);
738 g_return_val_if_fail(bp != NULL, FALSE);
739 g_return_val_if_fail(ep != NULL, FALSE);
743 /* find end point of URI */
744 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
745 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
749 /* no punctuation at end of string */
751 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
752 * should pass some URI type to this function and decide on that whether
753 * to perform punctuation stripping */
755 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
757 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
767 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
769 return g_strndup(bp, ep - bp);
772 /* valid mail address characters */
773 #define IS_RFC822_CHAR(ch) \
778 !strchr("(),;<>\"", (ch)))
780 /* alphabet and number within 7bit ASCII */
781 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
782 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
784 static GHashTable *create_domain_tab(void)
786 static const gchar *toplvl_domains [] = {
788 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
789 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
790 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
791 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
792 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
793 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
794 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
795 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
796 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
797 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
798 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
799 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
800 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
801 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
802 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
803 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
804 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
805 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
806 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
807 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
808 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
809 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
810 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
811 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
812 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
813 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
816 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
818 g_return_val_if_fail(htab, NULL);
819 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
820 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
824 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
826 const gint MAX_LVL_DOM_NAME_LEN = 6;
827 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
828 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
831 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
834 for (p = buf; p < m && first < last; *p++ = *first++)
838 return g_hash_table_lookup(tab, buf) != NULL;
841 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
842 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
843 const gchar **bp, const gchar **ep)
845 /* more complex than the uri part because we need to scan back and forward starting from
846 * the scan position. */
847 gboolean result = FALSE;
848 const gchar *bp_ = NULL;
849 const gchar *ep_ = NULL;
850 static GHashTable *dom_tab;
851 const gchar *last_dot = NULL;
852 const gchar *prelast_dot = NULL;
853 const gchar *last_tld_char = NULL;
855 /* the informative part of the email address (describing the name
856 * of the email address owner) may contain quoted parts. the
857 * closure stack stores the last encountered quotes. */
858 gchar closure_stack[128];
859 gchar *ptr = closure_stack;
861 g_return_val_if_fail(start != NULL, FALSE);
862 g_return_val_if_fail(scanpos != NULL, FALSE);
863 g_return_val_if_fail(bp != NULL, FALSE);
864 g_return_val_if_fail(ep != NULL, FALSE);
867 dom_tab = create_domain_tab();
868 g_return_val_if_fail(dom_tab, FALSE);
870 /* scan start of address */
871 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
874 /* TODO: should start with an alnum? */
876 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
879 if (bp_ != scanpos) {
880 /* scan end of address */
881 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
883 prelast_dot = last_dot;
885 if (*(last_dot + 1) == '.') {
886 if (prelast_dot == NULL)
888 last_dot = prelast_dot;
893 /* TODO: really should terminate with an alnum? */
894 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
898 if (last_dot == NULL)
901 last_dot = prelast_dot;
902 if (last_dot == NULL || (scanpos + 1 >= last_dot))
906 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
907 if (*last_tld_char == '?')
910 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
917 if (!result) return FALSE;
919 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
920 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
923 /* see if this is <bracketed>; in this case we also scan for the informative part. */
924 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
927 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
928 #define IN_STACK() (ptr > closure_stack)
929 /* has underrun check */
930 #define POP_STACK() if(IN_STACK()) --ptr
931 /* has overrun check */
932 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
933 /* has underrun check */
934 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
938 /* scan for the informative part. */
939 for (bp_ -= 2; bp_ >= start; bp_--) {
940 /* if closure on the stack keep scanning */
941 if (PEEK_STACK() == *bp_) {
945 if (*bp_ == '\'' || *bp_ == '"') {
950 /* if nothing in the closure stack, do the special conditions
951 * the following if..else expression simply checks whether
952 * a token is acceptable. if not acceptable, the clause
953 * should terminate the loop with a 'break' */
956 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
957 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
958 /* hyphens are allowed, but only in
960 } else if (!ispunct(*bp_)) {
961 /* but anything not being a punctiation
964 break; /* anything else is rejected */
977 /* scan forward (should start with an alnum) */
978 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
988 #undef IS_RFC822_CHAR
990 static gchar *make_email_string(const gchar *bp, const gchar *ep)
992 /* returns a mailto: URI; mailto: is also used to detect the
993 * uri type later on in the button_pressed signal handler */
997 tmp = g_strndup(bp, ep - bp);
998 result = g_strconcat("mailto:", tmp, NULL);
1004 #define ADD_TXT_POS(bp_, ep_, pti_) \
1005 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1006 last = last->next; \
1007 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1008 last->next = NULL; \
1010 g_warning("alloc error scanning URIs\n"); \
1011 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
1016 /* textview_make_clickable_parts() - colorizes clickable parts */
1017 static void textview_make_clickable_parts(TextView *textview,
1020 GdkColor *uri_color,
1021 const gchar *linebuf)
1023 /* parse table - in order of priority */
1025 const gchar *needle; /* token */
1027 /* token search function */
1028 gchar *(*search) (const gchar *haystack,
1029 const gchar *needle);
1030 /* part parsing function */
1031 gboolean (*parse) (const gchar *start,
1032 const gchar *scanpos,
1035 /* part to URI function */
1036 gchar *(*build_uri) (const gchar *bp,
1040 static struct table parser[] = {
1041 {"http://", strcasestr, get_uri_part, make_uri_string},
1042 {"https://", strcasestr, get_uri_part, make_uri_string},
1043 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1044 {"www.", strcasestr, get_uri_part, make_uri_string},
1045 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1046 {"@", strcasestr, get_email_part, make_email_string}
1048 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1051 const gchar *walk, *bp, *ep;
1054 const gchar *bp, *ep; /* text position */
1055 gint pti; /* index in parse table */
1056 struct txtpos *next; /* next */
1057 } head = {NULL, NULL, 0, NULL}, *last = &head;
1059 GtkSText *text = GTK_STEXT(textview->text);
1061 /* parse for clickable parts, and build a list of begin and end positions */
1062 for (walk = linebuf, n = 0;;) {
1063 gint last_index = PARSE_ELEMS;
1064 gchar *scanpos = NULL;
1066 /* FIXME: this looks phony. scanning for anything in the parse table */
1067 for (n = 0; n < PARSE_ELEMS; n++) {
1070 tmp = parser[n].search(walk, parser[n].needle);
1072 if (scanpos == NULL || tmp < scanpos) {
1080 /* check if URI can be parsed */
1081 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1082 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1083 ADD_TXT_POS(bp, ep, last_index);
1087 strlen(parser[last_index].needle);
1092 /* colorize this line */
1094 const gchar *normal_text = linebuf;
1097 for (last = head.next; last != NULL;
1098 normal_text = last->ep, last = last->next) {
1101 uri = g_new(RemoteURI, 1);
1102 if (last->bp - normal_text > 0)
1103 gtk_stext_insert(text, font,
1106 last->bp - normal_text);
1107 uri->uri = parser[last->pti].build_uri(last->bp,
1109 uri->start = gtk_stext_get_point(text);
1110 gtk_stext_insert(text, font, uri_color,
1111 NULL, last->bp, last->ep - last->bp);
1112 uri->end = gtk_stext_get_point(text);
1113 textview->uri_list =
1114 g_slist_append(textview->uri_list, uri);
1118 gtk_stext_insert(text, font, fg_color,
1119 NULL, normal_text, -1);
1121 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1126 /* This function writes str as a double-clickable link with the given url. */
1127 static void textview_write_link(TextView *textview, const gchar *url,
1128 const gchar *str, CodeConverter *conv)
1130 GdkColor *link_color = NULL;
1132 GtkSText *text = GTK_STEXT(textview->text);
1133 gchar buf[BUFFSIZE];
1135 /* this part is taken from textview_write_line. Right now the only place
1136 * that calls this function passes NULL for conv, but you never know. */
1139 strncpy2(buf, str, sizeof(buf));
1140 else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1141 gtk_stext_insert(text, textview->msgfont,
1142 prefs_common.enable_color
1143 ? &error_color : NULL, NULL,
1144 "*** Warning: code conversion failed ***\n",
1150 if (!conv || conv_convert(conv, buf, sizeof(buf), str) < 0)
1151 strncpy2(buf, str, sizeof(buf));
1154 gtk_stext_insert(text, textview->msgfont, NULL, NULL, " ", 1);
1156 /* this part is based on the code in make_clickable_parts */
1157 if (prefs_common.enable_color) {
1158 link_color = &uri_color;
1160 uri = g_new(RemoteURI, 1);
1161 uri->uri = g_strdup(url);
1162 uri->start = gtk_stext_get_point(text);
1163 gtk_stext_insert(text, textview->msgfont, link_color, NULL, str,
1165 uri->end = gtk_stext_get_point(text);
1166 textview->uri_list = g_slist_append(textview->uri_list, uri);
1169 static void textview_write_line(TextView *textview, const gchar *str,
1170 CodeConverter *conv)
1172 GtkSText *text = GTK_STEXT(textview->text);
1173 gchar buf[BUFFSIZE];
1175 gint quotelevel = -1;
1178 if (textview->text_is_mb)
1179 conv_localetodisp(buf, sizeof(buf), str);
1181 strncpy2(buf, str, sizeof(buf));
1182 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1183 conv_localetodisp(buf, sizeof(buf), str);
1184 else if (textview->text_is_mb)
1185 conv_unreadable_locale(buf);
1188 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1191 /* change color of quotation
1192 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1193 Up to 3 levels of quotations are detected, and each
1194 level is colored using a different color. */
1195 if (prefs_common.enable_color
1196 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1197 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1199 /* set up the correct foreground color */
1200 if (quotelevel > 2) {
1201 /* recycle colors */
1202 if (prefs_common.recycle_quote_colors)
1209 if (quotelevel == -1)
1212 fg_color = "e_colors[quotelevel];
1214 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1215 fg_color = &signature_color;
1216 textview->is_in_signature = TRUE;
1219 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1220 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1222 if (prefs_common.enable_color)
1223 textview_make_clickable_parts(textview, textview->msgfont,
1224 fg_color, &uri_color, buf);
1226 textview_make_clickable_parts(textview, textview->msgfont,
1227 fg_color, NULL, buf);
1230 void textview_clear(TextView *textview)
1232 GtkSText *text = GTK_STEXT(textview->text);
1234 gtk_stext_freeze(text);
1235 gtk_stext_set_point(text, 0);
1236 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1237 gtk_stext_thaw(text);
1239 textview_uri_list_remove_all(textview->uri_list);
1240 textview->uri_list = NULL;
1242 textview->body_pos = 0;
1243 textview->cur_pos = 0;
1246 void textview_destroy(TextView *textview)
1248 textview_uri_list_remove_all(textview->uri_list);
1249 textview->uri_list = NULL;
1251 if (!textview->scrolledwin_sb->parent)
1252 gtk_widget_destroy(textview->scrolledwin_sb);
1253 if (!textview->scrolledwin_mb->parent)
1254 gtk_widget_destroy(textview->scrolledwin_mb);
1256 if (textview->msgfont)
1257 gdk_font_unref(textview->msgfont);
1258 if (textview->boldfont)
1259 gdk_font_unref(textview->boldfont);
1264 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1266 textview->show_all_headers = all_headers;
1269 void textview_set_font(TextView *textview, const gchar *codeset)
1271 gboolean use_fontset = TRUE;
1273 /* In multi-byte mode, GtkSText can't display 8bit characters
1274 correctly, so it must be single-byte mode. */
1275 if (MB_CUR_MAX > 1) {
1276 if (codeset && conv_get_current_charset() != C_UTF_8) {
1277 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1278 !g_strcasecmp(codeset, "BALTIC"))
1279 use_fontset = FALSE;
1280 else if (conv_get_current_charset() != C_EUC_JP &&
1281 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1282 !g_strncasecmp(codeset, "CP", 2) ||
1283 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1284 use_fontset = FALSE;
1287 use_fontset = FALSE;
1289 if (textview->text_is_mb && !use_fontset) {
1292 parent = textview->scrolledwin_mb->parent;
1293 gtkut_container_remove(GTK_CONTAINER(parent),
1294 textview->scrolledwin_mb);
1295 gtk_container_add(GTK_CONTAINER(parent),
1296 textview->scrolledwin_sb);
1298 textview->text = textview->text_sb;
1299 textview->text_is_mb = FALSE;
1300 } else if (!textview->text_is_mb && use_fontset) {
1303 parent = textview->scrolledwin_sb->parent;
1304 gtkut_container_remove(GTK_CONTAINER(parent),
1305 textview->scrolledwin_sb);
1306 gtk_container_add(GTK_CONTAINER(parent),
1307 textview->scrolledwin_mb);
1309 textview->text = textview->text_mb;
1310 textview->text_is_mb = TRUE;
1313 if (prefs_common.textfont) {
1318 text_mb_font->ascent = text_mb_font_orig_ascent;
1319 text_mb_font->descent = text_mb_font_orig_descent;
1321 font = gdk_fontset_load(prefs_common.textfont);
1322 if (font && text_mb_font != font) {
1324 gdk_font_unref(text_mb_font);
1325 text_mb_font = font;
1326 text_mb_font_orig_ascent = font->ascent;
1327 text_mb_font_orig_descent = font->descent;
1331 text_sb_font->ascent = text_sb_font_orig_ascent;
1332 text_sb_font->descent = text_sb_font_orig_descent;
1335 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1337 font = gtkut_font_load_from_fontset
1338 (prefs_common.textfont);
1339 if (font && text_sb_font != font) {
1341 gdk_font_unref(text_sb_font);
1342 text_sb_font = font;
1343 text_sb_font_orig_ascent = font->ascent;
1344 text_sb_font_orig_descent = font->descent;
1349 gint ascent, descent;
1351 descent = prefs_common.line_space / 2;
1352 ascent = prefs_common.line_space - descent;
1353 font->ascent += ascent;
1354 font->descent += descent;
1356 if (textview->msgfont)
1357 gdk_font_unref(textview->msgfont);
1358 textview->msgfont = font;
1363 if (!textview->boldfont && prefs_common.boldfont)
1364 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1366 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1382 H_ORGANIZATION = 11,
1385 void textview_set_position(TextView *textview, gint pos)
1389 gtk_stext_get_length(GTK_STEXT(textview->text));
1391 textview->cur_pos = pos;
1395 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1397 gchar buf[BUFFSIZE];
1398 GPtrArray *headers, *sorted_headers;
1399 GSList *disphdr_list;
1403 g_return_val_if_fail(fp != NULL, NULL);
1405 if (textview->show_all_headers)
1406 return procheader_get_header_array_asis(fp);
1408 if (!prefs_common.display_header) {
1409 while (fgets(buf, sizeof(buf), fp) != NULL)
1410 if (buf[0] == '\r' || buf[0] == '\n') break;
1414 headers = procheader_get_header_array_asis(fp);
1416 sorted_headers = g_ptr_array_new();
1418 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1419 disphdr_list = disphdr_list->next) {
1420 DisplayHeaderProp *dp =
1421 (DisplayHeaderProp *)disphdr_list->data;
1423 for (i = 0; i < headers->len; i++) {
1424 header = g_ptr_array_index(headers, i);
1426 if (procheader_headername_equal(header->name,
1429 procheader_header_free(header);
1431 g_ptr_array_add(sorted_headers, header);
1433 g_ptr_array_remove_index(headers, i);
1439 if (prefs_common.show_other_header) {
1440 for (i = 0; i < headers->len; i++) {
1441 header = g_ptr_array_index(headers, i);
1442 g_ptr_array_add(sorted_headers, header);
1444 g_ptr_array_free(headers, TRUE);
1446 procheader_header_array_destroy(headers);
1448 return sorted_headers;
1451 static void textview_show_header(TextView *textview, GPtrArray *headers)
1453 GtkSText *text = GTK_STEXT(textview->text);
1457 g_return_if_fail(headers != NULL);
1459 gtk_stext_freeze(text);
1461 for (i = 0; i < headers->len; i++) {
1462 header = g_ptr_array_index(headers, i);
1463 g_return_if_fail(header->name != NULL);
1465 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1467 if (header->name[strlen(header->name) - 1] != ' ')
1468 gtk_stext_insert(text, textview->boldfont,
1469 NULL, NULL, " ", 1);
1471 if (procheader_headername_equal(header->name, "Subject") ||
1472 procheader_headername_equal(header->name, "From") ||
1473 procheader_headername_equal(header->name, "To") ||
1474 procheader_headername_equal(header->name, "Cc"))
1475 unfold_line(header->body);
1477 if (textview->text_is_mb == TRUE)
1478 conv_unreadable_locale(header->body);
1480 if (prefs_common.enable_color &&
1481 (procheader_headername_equal(header->name, "X-Mailer") ||
1482 procheader_headername_equal(header->name,
1484 strstr(header->body, "Sylpheed") != NULL)
1485 gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1487 else if (prefs_common.enable_color) {
1488 textview_make_clickable_parts(textview,
1489 NULL, NULL, &uri_color,
1492 textview_make_clickable_parts(textview,
1496 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1499 gtk_stext_thaw(text);
1502 gboolean textview_search_string(TextView *textview, const gchar *str,
1505 GtkSText *text = GTK_STEXT(textview->text);
1510 gboolean found = FALSE;
1512 g_return_val_if_fail(str != NULL, FALSE);
1514 wcs = strdup_mbstowcs(str);
1515 g_return_val_if_fail(wcs != NULL, FALSE);
1517 pos = textview->cur_pos;
1518 if (pos < textview->body_pos)
1519 pos = textview->body_pos;
1520 text_len = gtk_stext_get_length(text);
1521 if (text_len - pos < len) {
1526 for (; pos < text_len; pos++) {
1527 if (text_len - pos < len) break;
1528 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1530 gtk_widget_hide(GTK_WIDGET(textview->scrolledwin));
1531 gtk_editable_set_position(GTK_EDITABLE(text),
1533 gtk_editable_select_region(GTK_EDITABLE(text),
1535 gtk_widget_show(GTK_WIDGET(textview->scrolledwin));
1536 textview_set_position(textview, pos + len);
1540 if (text_len - pos == len) break;
1546 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1549 GtkSText *text = GTK_STEXT(textview->text);
1554 gboolean found = FALSE;
1556 g_return_val_if_fail(str != NULL, FALSE);
1558 wcs = strdup_mbstowcs(str);
1559 g_return_val_if_fail(wcs != NULL, FALSE);
1561 pos = textview->cur_pos;
1562 text_len = gtk_stext_get_length(text);
1563 if (text_len - textview->body_pos < len) {
1567 if (pos <= textview->body_pos || text_len - pos < len)
1568 pos = text_len - len;
1570 for (; pos >= textview->body_pos; pos--) {
1571 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1573 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1574 gtk_editable_select_region(GTK_EDITABLE(text),
1576 textview_set_position(textview, pos - 1);
1580 if (pos == textview->body_pos) break;
1587 void textview_scroll_one_line(TextView *textview, gboolean up)
1589 GtkSText *text = GTK_STEXT(textview->text);
1592 if (prefs_common.enable_smooth_scroll) {
1593 textview_smooth_scroll_one_line(textview, up);
1598 upper = text->vadj->upper - text->vadj->page_size;
1599 if (text->vadj->value < upper) {
1600 text->vadj->value +=
1601 text->vadj->step_increment * 4;
1603 MIN(text->vadj->value, upper);
1604 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1608 if (text->vadj->value > 0.0) {
1609 text->vadj->value -=
1610 text->vadj->step_increment * 4;
1612 MAX(text->vadj->value, 0.0);
1613 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1619 gboolean textview_scroll_page(TextView *textview, gboolean up)
1621 GtkSText *text = GTK_STEXT(textview->text);
1625 if (prefs_common.enable_smooth_scroll)
1626 return textview_smooth_scroll_page(textview, up);
1628 if (prefs_common.scroll_halfpage)
1629 page_incr = text->vadj->page_increment / 2;
1631 page_incr = text->vadj->page_increment;
1634 upper = text->vadj->upper - text->vadj->page_size;
1635 if (text->vadj->value < upper) {
1636 text->vadj->value += page_incr;
1637 text->vadj->value = MIN(text->vadj->value, upper);
1638 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1643 if (text->vadj->value > 0.0) {
1644 text->vadj->value -= page_incr;
1645 text->vadj->value = MAX(text->vadj->value, 0.0);
1646 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1655 static void textview_smooth_scroll_do(TextView *textview,
1656 gfloat old_value, gfloat last_value,
1659 GtkSText *text = GTK_STEXT(textview->text);
1664 if (old_value < last_value) {
1665 change_value = last_value - old_value;
1668 change_value = old_value - last_value;
1672 gdk_key_repeat_disable();
1674 for (i = step; i <= change_value; i += step) {
1675 text->vadj->value = old_value + (up ? -i : i);
1676 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1680 text->vadj->value = last_value;
1681 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1683 gdk_key_repeat_restore();
1686 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1688 GtkSText *text = GTK_STEXT(textview->text);
1694 upper = text->vadj->upper - text->vadj->page_size;
1695 if (text->vadj->value < upper) {
1696 old_value = text->vadj->value;
1697 last_value = text->vadj->value +
1698 text->vadj->step_increment * 4;
1699 last_value = MIN(last_value, upper);
1701 textview_smooth_scroll_do(textview, old_value,
1703 prefs_common.scroll_step);
1706 if (text->vadj->value > 0.0) {
1707 old_value = text->vadj->value;
1708 last_value = text->vadj->value -
1709 text->vadj->step_increment * 4;
1710 last_value = MAX(last_value, 0.0);
1712 textview_smooth_scroll_do(textview, old_value,
1714 prefs_common.scroll_step);
1719 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1721 GtkSText *text = GTK_STEXT(textview->text);
1727 if (prefs_common.scroll_halfpage)
1728 page_incr = text->vadj->page_increment / 2;
1730 page_incr = text->vadj->page_increment;
1733 upper = text->vadj->upper - text->vadj->page_size;
1734 if (text->vadj->value < upper) {
1735 old_value = text->vadj->value;
1736 last_value = text->vadj->value + page_incr;
1737 last_value = MIN(last_value, upper);
1739 textview_smooth_scroll_do(textview, old_value,
1741 prefs_common.scroll_step);
1745 if (text->vadj->value > 0.0) {
1746 old_value = text->vadj->value;
1747 last_value = text->vadj->value - page_incr;
1748 last_value = MAX(last_value, 0.0);
1750 textview_smooth_scroll_do(textview, old_value,
1752 prefs_common.scroll_step);
1760 #define KEY_PRESS_EVENT_STOP() \
1761 if (gtk_signal_n_emissions_by_name \
1762 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1763 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1764 "key_press_event"); \
1767 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1770 SummaryView *summaryview = NULL;
1771 MessageView *messageview = textview->messageview;
1773 if (!event) return FALSE;
1774 if (messageview->mainwin)
1775 summaryview = messageview->mainwin->summaryview;
1777 switch (event->keyval) {
1792 summary_pass_key_press_event(summaryview, event);
1794 textview_scroll_page(textview, FALSE);
1797 textview_scroll_page(textview, TRUE);
1800 textview_scroll_one_line(textview,
1801 (event->state & GDK_MOD1_MASK) != 0);
1805 summary_pass_key_press_event(summaryview, event);
1814 if (messageview->type == MVIEW_MIME &&
1815 textview == messageview->mimeview->textview) {
1816 KEY_PRESS_EVENT_STOP();
1817 mimeview_pass_key_press_event(messageview->mimeview,
1824 event->window != messageview->mainwin->window->window) {
1825 GdkEventKey tmpev = *event;
1827 tmpev.window = messageview->mainwin->window->window;
1828 KEY_PRESS_EVENT_STOP();
1829 gtk_widget_event(messageview->mainwin->window,
1830 (GdkEvent *)&tmpev);
1838 static gint show_url_timeout_cb(gpointer data)
1840 TextView *textview = (TextView *)data;
1842 if (textview->messageview->mainwin)
1843 if (textview->show_url_msgid)
1844 gtk_statusbar_remove(GTK_STATUSBAR(
1845 textview->messageview->mainwin->statusbar),
1846 textview->messageview->mainwin->folderview_cid,
1847 textview->show_url_msgid);
1851 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1855 textview->last_buttonpress = event->type;
1859 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1863 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1866 ((event->button == 1)
1867 || event->button == 2 || event->button == 3)) {
1870 /* double click seems to set the cursor after the current
1871 * word. The cursor position needs fixing, otherwise the
1872 * last word of a clickable zone will not work */
1873 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1874 textview->cur_pos--;
1877 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1878 RemoteURI *uri = (RemoteURI *)cur->data;
1880 if (textview->cur_pos >= uri->start &&
1881 textview->cur_pos < uri->end) {
1882 /* single click: display url in statusbar */
1883 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1884 if (textview->messageview->mainwin) {
1885 if (textview->show_url_msgid) {
1886 gtk_timeout_remove(textview->show_url_timeout_tag);
1887 gtk_statusbar_remove(GTK_STATUSBAR(
1888 textview->messageview->mainwin->statusbar),
1889 textview->messageview->mainwin->folderview_cid,
1890 textview->show_url_msgid);
1891 textview->show_url_msgid = 0;
1893 textview->show_url_msgid = gtk_statusbar_push(
1894 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1895 textview->messageview->mainwin->folderview_cid,
1897 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1898 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1901 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1902 if (event->button == 3) {
1903 gchar *fromname, *fromaddress;
1906 fromaddress = g_strdup(uri->uri + 7);
1907 /* Hiroyuki: please put this function in utils.c! */
1908 fromname = procheader_get_fromname(fromaddress);
1909 extract_address(fromaddress);
1910 g_message("adding from textview %s <%s>", fromname, fromaddress);
1911 /* Add to address book - Match */
1912 addressbook_add_contact( fromname, fromaddress, NULL );
1914 g_free(fromaddress);
1917 PrefsAccount *account = NULL;
1918 FolderItem *folder_item;
1920 if (textview->messageview && textview->messageview->mainwin
1921 && textview->messageview->mainwin->summaryview
1922 && textview->messageview->mainwin->summaryview->folder_item) {
1923 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1924 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1925 account = account_find_from_id(folder_item->prefs->default_account);
1927 compose_new(account, uri->uri + 7, NULL);
1931 if (!g_strncasecmp(uri->uri, "pgp:", 4)) {
1932 GtkAdjustment *pos = gtk_scrolled_window_get_vadjustment(
1933 GTK_SCROLLED_WINDOW(textview->scrolledwin));
1934 gfloat vpos = pos->value;
1935 mimeview_check_signature(textview->messageview->mimeview);
1936 /* scroll back where we were */
1937 gtk_adjustment_set_value(pos, vpos);
1942 prefs_common.uri_cmd);
1948 textview->last_buttonpress = event->type;
1952 static void textview_uri_list_remove_all(GSList *uri_list)
1956 for (cur = uri_list; cur != NULL; cur = cur->next) {
1958 g_free(((RemoteURI *)cur->data)->uri);
1963 g_slist_free(uri_list);