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"
49 #include "addressbook.h"
50 #include "displayheader.h"
54 typedef struct _RemoteURI RemoteURI;
64 static GdkColor quote_colors[3] = {
65 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
66 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
67 {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
70 static GdkColor signature_color = {
77 static GdkColor uri_color = {
84 static GdkColor emphasis_color = {
92 static GdkColor error_color = {
101 static GdkColor good_sig_color = {
108 static GdkColor nocheck_sig_color = {
115 static GdkColor bad_sig_color = {
123 static void textview_show_ertf (TextView *textview,
125 CodeConverter *conv);
126 static void textview_add_part (TextView *textview,
129 static void textview_add_parts (TextView *textview,
132 static void textview_write_body (TextView *textview,
135 const gchar *charset);
136 static void textview_show_html (TextView *textview,
138 CodeConverter *conv);
139 static void textview_write_line (TextView *textview,
141 CodeConverter *conv);
142 static void textview_write_link (TextView *textview,
145 CodeConverter *conv);
146 static GPtrArray *textview_scan_header (TextView *textview,
148 static void textview_show_header (TextView *textview,
151 static gint textview_key_pressed (GtkWidget *widget,
156 static gint textview_button_pressed (GtkWidget *widget,
157 GdkEventButton *event,
159 static gint textview_button_released (GtkWidget *widget,
160 GdkEventButton *event,
163 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
164 GdkEvent *event, GtkTextIter *iter,
168 static void textview_uri_list_remove_all(GSList *uri_list);
170 static void textview_smooth_scroll_do (TextView *textview,
174 static void textview_smooth_scroll_one_line (TextView *textview,
176 static gboolean textview_smooth_scroll_page (TextView *textview,
180 TextView *textview_create(void)
184 GtkWidget *scrolledwin;
186 GtkTextBuffer *buffer;
187 GtkClipboard *clipboard;
188 PangoFontDescription *font_desc = NULL;
190 debug_print("Creating text view...\n");
191 textview = g_new0(TextView, 1);
193 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
194 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
195 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
196 gtk_widget_set_size_request(scrolledwin, prefs_common.mainview_width, -1);
198 /* create GtkSText widgets for single-byte and multi-byte character */
199 text = gtk_text_view_new();
200 gtk_widget_show(text);
201 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
202 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
204 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
205 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
206 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
208 gtk_widget_ensure_style(text);
210 if (prefs_common.normalfont)
211 font_desc = pango_font_description_from_string
212 (prefs_common.normalfont);
214 gtk_widget_modify_font(text, font_desc);
216 pango_font_description_free(font_desc);
218 gtk_widget_ref(scrolledwin);
220 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
222 g_signal_connect(G_OBJECT(text), "key_press_event",
223 G_CALLBACK(textview_key_pressed),
226 gtk_widget_show(scrolledwin);
228 vbox = gtk_vbox_new(FALSE, 0);
229 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
231 gtk_widget_show(vbox);
233 textview->vbox = vbox;
234 textview->scrolledwin = scrolledwin;
235 textview->text = text;
236 textview->uri_list = NULL;
237 textview->body_pos = 0;
238 textview->show_all_headers = FALSE;
239 textview->last_buttonpress = GDK_NOTHING;
240 textview->show_url_msgid = 0;
245 static void textview_create_tags(GtkTextView *text, TextView *textview)
247 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
250 gtk_text_buffer_create_tag(buffer, "header",
251 "pixels-above-lines", 0,
252 "pixels-above-lines-set", TRUE,
253 "pixels-below-lines", 0,
254 "pixels-below-lines-set", TRUE,
256 "left-margin-set", TRUE,
258 gtk_text_buffer_create_tag(buffer, "header_title",
259 "font", prefs_common.boldfont,
261 gtk_text_buffer_create_tag(buffer, "quote0",
262 "foreground-gdk", "e_colors[0],
264 gtk_text_buffer_create_tag(buffer, "quote1",
265 "foreground-gdk", "e_colors[1],
267 gtk_text_buffer_create_tag(buffer, "quote2",
268 "foreground-gdk", "e_colors[2],
270 gtk_text_buffer_create_tag(buffer, "emphasis",
271 "foreground-gdk", &emphasis_color,
273 gtk_text_buffer_create_tag(buffer, "signature",
274 "foreground-gdk", &signature_color,
276 tag = gtk_text_buffer_create_tag(buffer, "link",
277 "foreground-gdk", &uri_color,
280 gtk_text_buffer_create_tag(buffer, "good-signature",
281 "foreground-gdk", &good_sig_color,
283 gtk_text_buffer_create_tag(buffer, "bad-signature",
284 "foreground-gdk", &bad_sig_color,
286 gtk_text_buffer_create_tag(buffer, "nocheck-signature",
287 "foreground-gdk", &nocheck_sig_color,
289 #endif /*USE_GPGME */
291 g_signal_connect(G_OBJECT(tag), "event",
292 G_CALLBACK(textview_uri_button_pressed), textview);
295 void textview_init(TextView *textview)
299 gtkut_widget_disable_theme_engine(textview->text);
301 textview_update_message_colors();
302 textview_set_all_headers(textview, FALSE);
303 textview_set_font(textview, NULL);
305 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
308 void textview_update_message_colors(void)
310 GdkColor black = {0, 0, 0, 0};
312 if (prefs_common.enable_color) {
313 /* grab the quote colors, converting from an int to a GdkColor */
314 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
316 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
318 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
320 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
322 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
325 quote_colors[0] = quote_colors[1] = quote_colors[2] =
326 uri_color = emphasis_color = signature_color = black;
330 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
334 const gchar *charset = NULL;
335 GPtrArray *headers = NULL;
337 if ((fp = fopen(file, "rb")) == NULL) {
338 FILE_OP_ERROR(file, "fopen");
342 if (textview->messageview->forced_charset)
343 charset = textview->messageview->forced_charset;
344 else if (prefs_common.force_charset)
345 charset = prefs_common.force_charset;
346 else if (mimeinfo->charset)
347 charset = mimeinfo->charset;
349 textview_set_font(textview, charset);
350 textview_clear(textview);
352 if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) perror("fseek");
353 headers = textview_scan_header(textview, fp);
355 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
356 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
359 textview_show_header(textview, headers);
360 procheader_header_array_destroy(headers);
362 gtk_text_buffer_get_end_iter(buffer, &iter);
363 textview->body_pos = gtk_text_iter_get_offset(&iter);
366 textview_add_parts(textview, mimeinfo, fp);
370 textview_set_position(textview, 0);
373 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
376 const gchar *boundary = NULL;
377 gint boundary_len = 0;
378 const gchar *charset = NULL;
379 GPtrArray *headers = NULL;
380 gboolean is_rfc822_part = FALSE;
382 g_return_if_fail(mimeinfo != NULL);
383 g_return_if_fail(fp != NULL);
385 if (mimeinfo->mime_type == MIME_MULTIPART) {
386 textview_clear(textview);
387 textview_add_parts(textview, mimeinfo, fp);
391 if (mimeinfo->parent && mimeinfo->parent->boundary) {
392 boundary = mimeinfo->parent->boundary;
393 boundary_len = strlen(boundary);
396 if (!boundary && (mimeinfo->mime_type == MIME_TEXT ||
397 mimeinfo->mime_type == MIME_TEXT_HTML ||
398 mimeinfo->mime_type == MIME_TEXT_ENRICHED)) {
400 if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0)
402 headers = textview_scan_header(textview, fp);
404 if (mimeinfo->mime_type == MIME_TEXT && mimeinfo->parent) {
406 MimeInfo *parent = mimeinfo->parent;
408 while (parent->parent) {
410 parent->main->mime_type ==
413 parent = parent->parent;
416 if ((fpos = ftell(fp)) < 0)
418 else if (fseek(fp, parent->fpos, SEEK_SET) < 0)
421 headers = textview_scan_header(textview, fp);
422 if (fseek(fp, fpos, SEEK_SET) < 0)
426 /* skip MIME part headers */
427 while (fgets(buf, sizeof(buf), fp) != NULL)
428 if (buf[0] == '\r' || buf[0] == '\n') break;
431 /* display attached RFC822 single text message */
432 if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
433 if (headers) procheader_header_array_destroy(headers);
434 if (!mimeinfo->sub) {
435 textview_clear(textview);
438 headers = textview_scan_header(textview, fp);
439 mimeinfo = mimeinfo->sub;
440 is_rfc822_part = TRUE;
443 if (textview->messageview->forced_charset)
444 charset = textview->messageview->forced_charset;
445 else if (prefs_common.force_charset)
446 charset = prefs_common.force_charset;
447 else if (mimeinfo->charset)
448 charset = mimeinfo->charset;
450 textview_set_font(textview, charset);
452 textview_clear(textview);
455 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
456 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
459 textview_show_header(textview, headers);
460 procheader_header_array_destroy(headers);
462 gtk_text_buffer_get_end_iter(buffer, &iter);
463 textview->body_pos = gtk_text_iter_get_offset(&iter);
464 if (!mimeinfo->main) {
465 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
469 if (mimeinfo->mime_type == MIME_MULTIPART || is_rfc822_part)
470 textview_add_parts(textview, mimeinfo, fp);
472 textview_write_body(textview, mimeinfo, fp, charset);
475 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
478 GtkTextBuffer *buffer;
481 const gchar *boundary = NULL;
482 gint boundary_len = 0;
483 const gchar *charset = NULL;
484 GPtrArray *headers = NULL;
486 g_return_if_fail(mimeinfo != NULL);
487 g_return_if_fail(fp != NULL);
489 text = GTK_TEXT_VIEW(textview->text);
490 buffer = gtk_text_view_get_buffer(text);
491 gtk_text_buffer_get_end_iter(buffer, &iter);
493 if (mimeinfo->mime_type == MIME_MULTIPART) return;
495 if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) {
500 if (mimeinfo->parent && mimeinfo->parent->boundary) {
501 boundary = mimeinfo->parent->boundary;
502 boundary_len = strlen(boundary);
505 while (fgets(buf, sizeof(buf), fp) != NULL)
506 if (buf[0] == '\r' || buf[0] == '\n') break;
508 if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
509 headers = textview_scan_header(textview, fp);
511 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
512 textview_show_header(textview, headers);
513 procheader_header_array_destroy(headers);
519 if (mimeinfo->sigstatus)
520 g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n",
521 mimeinfo->content_type, mimeinfo->sigstatus);
524 if (mimeinfo->filename || mimeinfo->name)
525 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
526 mimeinfo->filename ? mimeinfo->filename :
528 mimeinfo->content_type, mimeinfo->size);
530 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
531 mimeinfo->content_type, mimeinfo->size);
534 if (mimeinfo->sigstatus && !mimeinfo->sigstatus_full) {
538 /* use standard font */
539 gpointer oldfont = textview->msgfont;
540 textview->msgfont = NULL;
542 tmp = g_strconcat("pgp: ", _("Check signature"), NULL);
543 textview_write_link(textview, buf, tmp, NULL);
545 /* put things back */
546 textview->msgfont = (GdkFont *)oldfont;
552 tmp = g_strconcat("pgp: ", _("Check signature"), NULL);
553 textview_write_link(textview, buf, tmp, NULL);
555 } else if (mimeinfo->sigstatus) {
557 if (!strcmp(mimeinfo->sigstatus, _("Good signature")))
558 color = "good-signature";
559 else if (!strcmp(mimeinfo->sigstatus, _("BAD signature")))
560 color = "bad-signature";
562 color = "nocheck-signature";
563 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
567 if (mimeinfo->mime_type != MIME_TEXT &&
568 mimeinfo->mime_type != MIME_TEXT_HTML &&
569 mimeinfo->mime_type != MIME_TEXT_ENRICHED) {
570 gtk_text_buffer_insert(buffer, &iter, buf, -1);
572 if (!mimeinfo->main &&
574 mimeinfo->parent->children != mimeinfo)
575 gtk_text_buffer_insert(buffer, &iter, buf, -1);
576 else if (prefs_common.display_header)
577 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
578 if (textview->messageview->forced_charset)
579 charset = textview->messageview->forced_charset;
580 else if (prefs_common.force_charset)
581 charset = prefs_common.force_charset;
582 else if (mimeinfo->charset)
583 charset = mimeinfo->charset;
584 textview_write_body(textview, mimeinfo, fp, charset);
588 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
592 g_return_if_fail(mimeinfo != NULL);
593 g_return_if_fail(fp != NULL);
595 level = mimeinfo->level;
598 textview_add_part(textview, mimeinfo, fp);
599 if (mimeinfo->parent && mimeinfo->parent->content_type &&
600 !strcasecmp(mimeinfo->parent->content_type,
601 "multipart/alternative"))
602 mimeinfo = mimeinfo->parent->next;
604 mimeinfo = procmime_mimeinfo_next(mimeinfo);
605 if (!mimeinfo || mimeinfo->level <= level)
610 #define TEXT_INSERT(str) \
611 gtk_text_buffer_insert(buffer, &iter, str, -1)
613 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
616 GtkTextBuffer *buffer;
619 if (!partinfo) return;
621 textview_set_font(textview, NULL);
622 textview_clear(textview);
624 text = GTK_TEXT_VIEW(textview->text);
625 buffer = gtk_text_view_get_buffer(text);
626 gtk_text_buffer_get_start_iter(buffer, &iter);
628 TEXT_INSERT(_("To save this part, pop up the context menu with "));
629 TEXT_INSERT(_("right click and select `Save as...', "));
630 TEXT_INSERT(_("or press `y' key.\n\n"));
632 TEXT_INSERT(_("To display this part as a text message, select "));
633 TEXT_INSERT(_("`Display as text', or press `t' key.\n\n"));
635 TEXT_INSERT(_("To open this part with external program, select "));
636 TEXT_INSERT(_("`Open' or `Open with...', "));
637 TEXT_INSERT(_("or double-click, or click the center button, "));
638 TEXT_INSERT(_("or press `l' key."));
642 void textview_show_signature_part(TextView *textview, MimeInfo *partinfo)
645 GtkTextBuffer *buffer;
648 if (!partinfo) return;
650 textview_set_font(textview, NULL);
651 textview_clear(textview);
653 text = GTK_TEXT_VIEW(textview->text);
654 buffer = gtk_text_view_get_buffer(text);
655 gtk_text_buffer_get_start_iter(buffer, &iter);
657 if (partinfo->sigstatus_full == NULL) {
658 TEXT_INSERT(_("This signature has not been checked yet.\n"));
659 TEXT_INSERT(_("To check it, pop up the context menu with\n"));
660 TEXT_INSERT(_("right click and select `Check signature'.\n"));
662 TEXT_INSERT(partinfo->sigstatus_full);
665 #endif /* USE_GPGME */
669 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
670 FILE *fp, const gchar *charset)
676 conv = conv_code_converter_new(charset);
678 tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
680 textview->is_in_signature = FALSE;
683 if (mimeinfo->mime_type == MIME_TEXT_HTML)
684 textview_show_html(textview, tmpfp, conv);
685 else if (mimeinfo->mime_type == MIME_TEXT_ENRICHED)
686 textview_show_ertf(textview, tmpfp, conv);
688 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
689 textview_write_line(textview, buf, conv);
693 conv_code_converter_destroy(conv);
696 static void textview_show_html(TextView *textview, FILE *fp,
702 parser = html_parser_new(fp, conv);
703 g_return_if_fail(parser != NULL);
705 while ((str = html_parse(parser)) != NULL) {
706 if (parser->state == HTML_HREF) {
707 /* first time : get and copy the URL */
708 if (parser->href == NULL) {
709 /* ALF - the sylpheed html parser returns an empty string,
710 * if still inside an <a>, but already parsed past HREF */
711 str = strtok(str, " ");
713 parser->href = strdup(str);
714 /* the URL may (or not) be followed by the
716 str = strtok(NULL, "");
720 textview_write_link(textview, str, parser->href, NULL);
722 textview_write_line(textview, str, NULL);
724 html_parser_destroy(parser);
727 static void textview_show_ertf(TextView *textview, FILE *fp,
733 parser = ertf_parser_new(fp, conv);
734 g_return_if_fail(parser != NULL);
736 while ((str = ertf_parse(parser)) != NULL) {
737 textview_write_line(textview, str, NULL);
740 ertf_parser_destroy(parser);
743 /* get_uri_part() - retrieves a URI starting from scanpos.
744 Returns TRUE if succesful */
745 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
746 const gchar **bp, const gchar **ep)
750 g_return_val_if_fail(start != NULL, FALSE);
751 g_return_val_if_fail(scanpos != NULL, FALSE);
752 g_return_val_if_fail(bp != NULL, FALSE);
753 g_return_val_if_fail(ep != NULL, FALSE);
757 /* find end point of URI */
758 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
759 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
763 /* no punctuation at end of string */
765 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
766 * should pass some URI type to this function and decide on that whether
767 * to perform punctuation stripping */
769 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
771 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
781 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
783 return g_strndup(bp, ep - bp);
786 /* valid mail address characters */
787 #define IS_RFC822_CHAR(ch) \
792 !strchr("(),;<>\"", (ch)))
794 /* alphabet and number within 7bit ASCII */
795 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
796 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
798 static GHashTable *create_domain_tab(void)
800 static const gchar *toplvl_domains [] = {
802 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
803 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
804 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
805 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
806 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
807 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
808 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
809 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
810 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
811 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
812 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
813 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
814 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
815 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
816 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
817 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
818 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
819 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
820 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
821 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
822 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
823 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
824 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
825 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
826 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
827 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
830 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
832 g_return_val_if_fail(htab, NULL);
833 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
834 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
838 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
840 const gint MAX_LVL_DOM_NAME_LEN = 6;
841 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
842 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
845 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
848 for (p = buf; p < m && first < last; *p++ = *first++)
852 return g_hash_table_lookup(tab, buf) != NULL;
855 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
856 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
857 const gchar **bp, const gchar **ep)
859 /* more complex than the uri part because we need to scan back and forward starting from
860 * the scan position. */
861 gboolean result = FALSE;
862 const gchar *bp_ = NULL;
863 const gchar *ep_ = NULL;
864 static GHashTable *dom_tab;
865 const gchar *last_dot = NULL;
866 const gchar *prelast_dot = NULL;
867 const gchar *last_tld_char = NULL;
869 /* the informative part of the email address (describing the name
870 * of the email address owner) may contain quoted parts. the
871 * closure stack stores the last encountered quotes. */
872 gchar closure_stack[128];
873 gchar *ptr = closure_stack;
875 g_return_val_if_fail(start != NULL, FALSE);
876 g_return_val_if_fail(scanpos != NULL, FALSE);
877 g_return_val_if_fail(bp != NULL, FALSE);
878 g_return_val_if_fail(ep != NULL, FALSE);
881 dom_tab = create_domain_tab();
882 g_return_val_if_fail(dom_tab, FALSE);
884 /* scan start of address */
885 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
888 /* TODO: should start with an alnum? */
890 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
893 if (bp_ != scanpos) {
894 /* scan end of address */
895 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
897 prelast_dot = last_dot;
899 if (*(last_dot + 1) == '.') {
900 if (prelast_dot == NULL)
902 last_dot = prelast_dot;
907 /* TODO: really should terminate with an alnum? */
908 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
912 if (last_dot == NULL)
915 last_dot = prelast_dot;
916 if (last_dot == NULL || (scanpos + 1 >= last_dot))
920 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
921 if (*last_tld_char == '?')
924 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
931 if (!result) return FALSE;
933 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
934 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
937 /* see if this is <bracketed>; in this case we also scan for the informative part. */
938 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
941 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
942 #define IN_STACK() (ptr > closure_stack)
943 /* has underrun check */
944 #define POP_STACK() if(IN_STACK()) --ptr
945 /* has overrun check */
946 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
947 /* has underrun check */
948 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
952 /* scan for the informative part. */
953 for (bp_ -= 2; bp_ >= start; bp_--) {
954 /* if closure on the stack keep scanning */
955 if (PEEK_STACK() == *bp_) {
959 if (*bp_ == '\'' || *bp_ == '"') {
964 /* if nothing in the closure stack, do the special conditions
965 * the following if..else expression simply checks whether
966 * a token is acceptable. if not acceptable, the clause
967 * should terminate the loop with a 'break' */
970 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
971 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
972 /* hyphens are allowed, but only in
974 } else if (!ispunct(*bp_)) {
975 /* but anything not being a punctiation
978 break; /* anything else is rejected */
991 /* scan forward (should start with an alnum) */
992 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
1002 #undef IS_RFC822_CHAR
1004 static gchar *make_email_string(const gchar *bp, const gchar *ep)
1006 /* returns a mailto: URI; mailto: is also used to detect the
1007 * uri type later on in the button_pressed signal handler */
1011 tmp = g_strndup(bp, ep - bp);
1012 result = g_strconcat("mailto:", tmp, NULL);
1018 #define ADD_TXT_POS(bp_, ep_, pti_) \
1019 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1020 last = last->next; \
1021 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1022 last->next = NULL; \
1024 g_warning("alloc error scanning URIs\n"); \
1025 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1031 /* textview_make_clickable_parts() - colorizes clickable parts */
1032 static void textview_make_clickable_parts(TextView *textview,
1033 const gchar *fg_tag,
1034 const gchar *uri_tag,
1035 const gchar *linebuf)
1037 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1038 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1041 /* parse table - in order of priority */
1043 const gchar *needle; /* token */
1045 /* token search function */
1046 gchar *(*search) (const gchar *haystack,
1047 const gchar *needle);
1048 /* part parsing function */
1049 gboolean (*parse) (const gchar *start,
1050 const gchar *scanpos,
1053 /* part to URI function */
1054 gchar *(*build_uri) (const gchar *bp,
1058 static struct table parser[] = {
1059 {"http://", strcasestr, get_uri_part, make_uri_string},
1060 {"https://", strcasestr, get_uri_part, make_uri_string},
1061 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1062 {"www.", strcasestr, get_uri_part, make_uri_string},
1063 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1064 {"@", strcasestr, get_email_part, make_email_string}
1066 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1069 const gchar *walk, *bp, *ep;
1072 const gchar *bp, *ep; /* text position */
1073 gint pti; /* index in parse table */
1074 struct txtpos *next; /* next */
1075 } head = {NULL, NULL, 0, NULL}, *last = &head;
1077 gtk_text_buffer_get_end_iter(buffer, &iter);
1079 /* parse for clickable parts, and build a list of begin and end positions */
1080 for (walk = linebuf, n = 0;;) {
1081 gint last_index = PARSE_ELEMS;
1082 gchar *scanpos = NULL;
1084 /* FIXME: this looks phony. scanning for anything in the parse table */
1085 for (n = 0; n < PARSE_ELEMS; n++) {
1088 tmp = parser[n].search(walk, parser[n].needle);
1090 if (scanpos == NULL || tmp < scanpos) {
1098 /* check if URI can be parsed */
1099 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1100 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1101 ADD_TXT_POS(bp, ep, last_index);
1105 strlen(parser[last_index].needle);
1110 /* colorize this line */
1112 const gchar *normal_text = linebuf;
1115 for (last = head.next; last != NULL;
1116 normal_text = last->ep, last = last->next) {
1119 uri = g_new(RemoteURI, 1);
1120 if (last->bp - normal_text > 0)
1121 gtk_text_buffer_insert_with_tags_by_name
1124 last->bp - normal_text,
1126 uri->uri = parser[last->pti].build_uri(last->bp,
1128 uri->start = gtk_text_iter_get_offset(&iter);
1129 gtk_text_buffer_insert_with_tags_by_name
1131 last->bp, last->ep - last->bp,
1133 uri->end = gtk_text_iter_get_offset(&iter);
1134 textview->uri_list =
1135 g_slist_append(textview->uri_list, uri);
1139 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1143 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1151 static void textview_write_line(TextView *textview, const gchar *str,
1152 CodeConverter *conv)
1155 GtkTextBuffer *buffer;
1157 gchar buf[BUFFSIZE];
1159 gint quotelevel = -1;
1160 gchar quote_tag_str[10];
1162 text = GTK_TEXT_VIEW(textview->text);
1163 buffer = gtk_text_view_get_buffer(text);
1164 gtk_text_buffer_get_end_iter(buffer, &iter);
1169 if (textview->text_is_mb)
1170 conv_localetodisp(buf, sizeof(buf), str);
1172 strncpy2(buf, str, sizeof(buf));
1173 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1174 conv_localetodisp(buf, sizeof(buf), str);
1175 else if (textview->text_is_mb)
1176 conv_unreadable_locale(buf);
1179 strncpy2(buf, str, sizeof(buf));
1180 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1181 conv_localetodisp(buf, sizeof(buf), str);
1185 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1188 /* change color of quotation
1189 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1190 Up to 3 levels of quotations are detected, and each
1191 level is colored using a different color. */
1192 if (prefs_common.enable_color
1193 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1194 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1196 /* set up the correct foreground color */
1197 if (quotelevel > 2) {
1198 /* recycle colors */
1199 if (prefs_common.recycle_quote_colors)
1206 if (quotelevel == -1)
1209 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1210 "quote%d", quotelevel);
1211 fg_color = quote_tag_str;
1214 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1215 fg_color = "signature";
1216 textview->is_in_signature = TRUE;
1219 if (prefs_common.enable_color)
1220 textview_make_clickable_parts(textview, fg_color, "link", buf);
1222 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1225 void textview_write_link(TextView *textview, const gchar *str,
1226 const gchar *uri, CodeConverter *conv)
1228 GdkColor *link_color = NULL;
1230 GtkTextBuffer *buffer;
1232 gchar buf[BUFFSIZE];
1239 text = GTK_TEXT_VIEW(textview->text);
1240 buffer = gtk_text_view_get_buffer(text);
1241 gtk_text_buffer_get_end_iter(buffer, &iter);
1246 if (textview->text_is_mb)
1247 conv_localetodisp(buf, sizeof(buf), str);
1249 strncpy2(buf, str, sizeof(buf));
1250 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1251 conv_localetodisp(buf, sizeof(buf), str);
1252 else if (textview->text_is_mb)
1253 conv_unreadable_locale(buf);
1256 strncpy2(buf, str, sizeof(buf));
1257 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1258 conv_localetodisp(buf, sizeof(buf), str);
1263 gtk_text_buffer_get_end_iter(buffer, &iter);
1265 for (bufp = buf; isspace(*bufp); bufp++)
1266 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1268 if (prefs_common.enable_color) {
1269 link_color = &uri_color;
1271 r_uri = g_new(RemoteURI, 1);
1272 r_uri->uri = g_strdup(uri);
1273 r_uri->start = gtk_text_iter_get_offset(&iter);
1274 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1276 r_uri->end = gtk_text_iter_get_offset(&iter);
1277 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1280 void textview_clear(TextView *textview)
1282 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1283 GtkTextBuffer *buffer;
1285 buffer = gtk_text_view_get_buffer(text);
1286 gtk_text_buffer_set_text(buffer, "\0", -1);
1288 textview_uri_list_remove_all(textview->uri_list);
1289 textview->uri_list = NULL;
1291 textview->body_pos = 0;
1294 void textview_destroy(TextView *textview)
1296 textview_uri_list_remove_all(textview->uri_list);
1297 textview->uri_list = NULL;
1302 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1304 textview->show_all_headers = all_headers;
1307 void textview_set_font(TextView *textview, const gchar *codeset)
1309 if (prefs_common.textfont) {
1310 PangoFontDescription *font_desc = NULL;
1312 if (prefs_common.textfont)
1313 font_desc = pango_font_description_from_string
1314 (prefs_common.textfont);
1316 gtk_widget_modify_font(textview->text, font_desc);
1317 pango_font_description_free(font_desc);
1320 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1321 prefs_common.line_space / 2);
1322 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1323 prefs_common.line_space / 2);
1324 if (prefs_common.head_space) {
1325 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1327 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1344 H_ORGANIZATION = 11,
1347 void textview_set_position(TextView *textview, gint pos)
1349 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1350 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1353 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1354 gtk_text_buffer_place_cursor(buffer, &iter);
1357 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1359 gchar buf[BUFFSIZE];
1360 GPtrArray *headers, *sorted_headers;
1361 GSList *disphdr_list;
1365 g_return_val_if_fail(fp != NULL, NULL);
1367 if (textview->show_all_headers)
1368 return procheader_get_header_array_asis(fp);
1370 if (!prefs_common.display_header) {
1371 while (fgets(buf, sizeof(buf), fp) != NULL)
1372 if (buf[0] == '\r' || buf[0] == '\n') break;
1376 headers = procheader_get_header_array_asis(fp);
1378 sorted_headers = g_ptr_array_new();
1380 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1381 disphdr_list = disphdr_list->next) {
1382 DisplayHeaderProp *dp =
1383 (DisplayHeaderProp *)disphdr_list->data;
1385 for (i = 0; i < headers->len; i++) {
1386 header = g_ptr_array_index(headers, i);
1388 if (procheader_headername_equal(header->name,
1391 procheader_header_free(header);
1393 g_ptr_array_add(sorted_headers, header);
1395 g_ptr_array_remove_index(headers, i);
1401 if (prefs_common.show_other_header) {
1402 for (i = 0; i < headers->len; i++) {
1403 header = g_ptr_array_index(headers, i);
1404 g_ptr_array_add(sorted_headers, header);
1406 g_ptr_array_free(headers, TRUE);
1408 procheader_header_array_destroy(headers);
1411 return sorted_headers;
1414 static void textview_show_header(TextView *textview, GPtrArray *headers)
1416 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1417 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1422 g_return_if_fail(headers != NULL);
1424 for (i = 0; i < headers->len; i++) {
1425 header = g_ptr_array_index(headers, i);
1426 g_return_if_fail(header->name != NULL);
1428 gtk_text_buffer_get_end_iter (buffer, &iter);
1429 gtk_text_buffer_insert_with_tags_by_name
1430 (buffer, &iter, header->name, -1,
1431 "header_title", "header", NULL);
1432 if (header->name[strlen(header->name) - 1] != ' ')
1435 gtk_stext_insert(text, textview->boldfont,
1436 NULL, NULL, " ", 1);
1438 gtk_text_buffer_insert_with_tags_by_name
1439 (buffer, &iter, " ", 1,
1440 "header_title", "header", NULL);
1443 if (procheader_headername_equal(header->name, "Subject") ||
1444 procheader_headername_equal(header->name, "From") ||
1445 procheader_headername_equal(header->name, "To") ||
1446 procheader_headername_equal(header->name, "Cc"))
1447 unfold_line(header->body);
1451 if (textview->text_is_mb == TRUE)
1452 conv_unreadable_locale(header->body);
1455 if (prefs_common.enable_color &&
1456 (procheader_headername_equal(header->name, "X-Mailer") ||
1457 procheader_headername_equal(header->name,
1459 strstr(header->body, "Sylpheed") != NULL) {
1460 gtk_text_buffer_get_end_iter (buffer, &iter);
1461 gtk_text_buffer_insert_with_tags_by_name
1462 (buffer, &iter, header->body, -1,
1463 "header", "emphasis", NULL);
1464 } else if (prefs_common.enable_color) {
1465 textview_make_clickable_parts(textview, "header", "link",
1468 textview_make_clickable_parts(textview, "header", NULL,
1471 gtk_text_buffer_get_end_iter (buffer, &iter);
1472 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1477 gboolean textview_search_string(TextView *textview, const gchar *str,
1480 #warning FIXME_GTK2 /* currently, these search functions ignores case_sens */
1482 GtkSText *text = GTK_STEXT(textview->text);
1486 g_return_val_if_fail(str != NULL, FALSE);
1488 len = get_mbs_len(str);
1489 g_return_val_if_fail(len >= 0, FALSE);
1491 pos = textview->cur_pos;
1492 if (pos < textview->body_pos)
1493 pos = textview->body_pos;
1495 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1496 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1497 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1498 textview_set_position(textview, pos + len);
1504 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1505 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1507 GtkTextIter iter, start, end, *pos;
1509 gint insert_offset, selbound_offset;
1511 /* reset selection */
1512 mark = gtk_text_buffer_get_mark(buffer, "insert");
1513 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1514 insert_offset = gtk_text_iter_get_offset(&start);
1515 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1516 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1517 selbound_offset = gtk_text_iter_get_offset(&end);
1519 pos = insert_offset > selbound_offset ? &start : &end;
1520 gtk_text_buffer_place_cursor(buffer, pos);
1523 mark = gtk_text_buffer_get_insert(buffer);
1524 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1525 found = gtk_text_iter_forward_search(&iter, str,
1526 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1527 &start, &end, NULL);
1529 gtk_text_buffer_place_cursor(buffer, &start);
1530 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", &end);
1531 mark = gtk_text_buffer_get_mark(buffer, "insert");
1532 gtk_text_view_scroll_mark_onscreen(text, mark);
1539 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1544 GtkSText *text = GTK_STEXT(textview->text);
1549 gboolean found = FALSE;
1551 g_return_val_if_fail(str != NULL, FALSE);
1553 wcs = strdup_mbstowcs(str);
1554 g_return_val_if_fail(wcs != NULL, FALSE);
1556 pos = textview->cur_pos;
1557 text_len = gtk_stext_get_length(text);
1558 if (text_len - textview->body_pos < len) {
1562 if (pos <= textview->body_pos || text_len - pos < len)
1563 pos = text_len - len;
1565 for (; pos >= textview->body_pos; pos--) {
1566 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1568 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1569 gtk_editable_select_region(GTK_EDITABLE(text),
1571 textview_set_position(textview, pos - 1);
1575 if (pos == textview->body_pos) break;
1581 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1582 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1584 GtkTextIter iter, start, end, *pos;
1586 gint insert_offset, selbound_offset;
1588 /* reset selection */
1589 mark = gtk_text_buffer_get_mark(buffer, "insert");
1590 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1591 insert_offset = gtk_text_iter_get_offset(&start);
1592 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1593 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1594 selbound_offset = gtk_text_iter_get_offset(&end);
1596 pos = insert_offset < selbound_offset ? &start : &end;
1597 gtk_text_buffer_place_cursor(buffer, pos);
1600 mark = gtk_text_buffer_get_insert(buffer);
1601 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1602 found = gtk_text_iter_backward_search(&iter, str,
1603 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1604 &start, &end, NULL);
1606 gtk_text_buffer_place_cursor(buffer, &end);
1607 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", &start);
1608 mark = gtk_text_buffer_get_mark(buffer, "insert");
1609 gtk_text_view_scroll_mark_onscreen(text, mark);
1616 void textview_scroll_one_line(TextView *textview, gboolean up)
1618 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1619 GtkAdjustment *vadj = text->vadjustment;
1622 if (prefs_common.enable_smooth_scroll) {
1623 textview_smooth_scroll_one_line(textview, up);
1628 upper = vadj->upper - vadj->page_size;
1629 if (vadj->value < upper) {
1631 vadj->step_increment * 4;
1633 MIN(vadj->value, upper);
1634 g_signal_emit_by_name(G_OBJECT(vadj),
1635 "value_changed", 0);
1638 if (vadj->value > 0.0) {
1640 vadj->step_increment * 4;
1642 MAX(vadj->value, 0.0);
1643 g_signal_emit_by_name(G_OBJECT(vadj),
1644 "value_changed", 0);
1649 gboolean textview_scroll_page(TextView *textview, gboolean up)
1651 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1652 GtkAdjustment *vadj = text->vadjustment;
1656 if (prefs_common.enable_smooth_scroll)
1657 return textview_smooth_scroll_page(textview, up);
1659 if (prefs_common.scroll_halfpage)
1660 page_incr = vadj->page_increment / 2;
1662 page_incr = vadj->page_increment;
1665 upper = vadj->upper - vadj->page_size;
1666 if (vadj->value < upper) {
1667 vadj->value += page_incr;
1668 vadj->value = MIN(vadj->value, upper);
1669 g_signal_emit_by_name(G_OBJECT(vadj),
1670 "value_changed", 0);
1674 if (vadj->value > 0.0) {
1675 vadj->value -= page_incr;
1676 vadj->value = MAX(vadj->value, 0.0);
1677 g_signal_emit_by_name(G_OBJECT(vadj),
1678 "value_changed", 0);
1686 static void textview_smooth_scroll_do(TextView *textview,
1687 gfloat old_value, gfloat last_value,
1690 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1691 GtkAdjustment *vadj = text->vadjustment;
1696 if (old_value < last_value) {
1697 change_value = last_value - old_value;
1700 change_value = old_value - last_value;
1705 /* gdk_key_repeat_disable(); */
1707 for (i = step; i <= change_value; i += step) {
1708 vadj->value = old_value + (up ? -i : i);
1709 g_signal_emit_by_name(G_OBJECT(vadj),
1710 "value_changed", 0);
1713 vadj->value = last_value;
1714 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1717 /* gdk_key_repeat_restore(); */
1720 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1722 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1723 GtkAdjustment *vadj = text->vadjustment;
1729 upper = vadj->upper - vadj->page_size;
1730 if (vadj->value < upper) {
1731 old_value = vadj->value;
1732 last_value = vadj->value +
1733 vadj->step_increment * 4;
1734 last_value = MIN(last_value, upper);
1736 textview_smooth_scroll_do(textview, old_value,
1738 prefs_common.scroll_step);
1741 if (vadj->value > 0.0) {
1742 old_value = vadj->value;
1743 last_value = vadj->value -
1744 vadj->step_increment * 4;
1745 last_value = MAX(last_value, 0.0);
1747 textview_smooth_scroll_do(textview, old_value,
1749 prefs_common.scroll_step);
1754 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1756 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1757 GtkAdjustment *vadj = text->vadjustment;
1763 if (prefs_common.scroll_halfpage)
1764 page_incr = vadj->page_increment / 2;
1766 page_incr = vadj->page_increment;
1769 upper = vadj->upper - vadj->page_size;
1770 if (vadj->value < upper) {
1771 old_value = vadj->value;
1772 last_value = vadj->value + page_incr;
1773 last_value = MIN(last_value, upper);
1775 textview_smooth_scroll_do(textview, old_value,
1777 prefs_common.scroll_step);
1781 if (vadj->value > 0.0) {
1782 old_value = vadj->value;
1783 last_value = vadj->value - page_incr;
1784 last_value = MAX(last_value, 0.0);
1786 textview_smooth_scroll_do(textview, old_value,
1788 prefs_common.scroll_step);
1798 #define KEY_PRESS_EVENT_STOP() \
1799 if (gtk_signal_n_emissions_by_name \
1800 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1801 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1802 "key_press_event"); \
1805 #define KEY_PRESS_EVENT_STOP() \
1806 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1810 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1813 SummaryView *summaryview = NULL;
1814 MessageView *messageview = textview->messageview;
1816 if (!event) return FALSE;
1817 if (messageview->mainwin)
1818 summaryview = messageview->mainwin->summaryview;
1820 switch (event->keyval) {
1835 summary_pass_key_press_event(summaryview, event);
1837 textview_scroll_page(textview, FALSE);
1840 textview_scroll_page(textview, TRUE);
1843 textview_scroll_one_line(textview,
1844 (event->state & GDK_MOD1_MASK) != 0);
1848 summary_pass_key_press_event(summaryview, event);
1857 if (messageview->type == MVIEW_MIME &&
1858 textview == messageview->mimeview->textview) {
1859 KEY_PRESS_EVENT_STOP();
1860 mimeview_pass_key_press_event(messageview->mimeview,
1867 event->window != messageview->mainwin->window->window) {
1868 GdkEventKey tmpev = *event;
1870 tmpev.window = messageview->mainwin->window->window;
1871 KEY_PRESS_EVENT_STOP();
1872 gtk_widget_event(messageview->mainwin->window,
1873 (GdkEvent *)&tmpev);
1881 static gint show_url_timeout_cb(gpointer data)
1883 TextView *textview = (TextView *)data;
1885 if (textview->messageview->mainwin)
1886 if (textview->show_url_msgid)
1887 gtk_statusbar_remove(GTK_STATUSBAR(
1888 textview->messageview->mainwin->statusbar),
1889 textview->messageview->mainwin->folderview_cid,
1890 textview->show_url_msgid);
1896 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1900 textview->last_buttonpress = event->type;
1904 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1908 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1911 ((event->button == 1)
1912 || event->button == 2 || event->button == 3)) {
1915 /* double click seems to set the cursor after the current
1916 * word. The cursor position needs fixing, otherwise the
1917 * last word of a clickable zone will not work */
1918 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1919 textview->cur_pos--;
1922 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1923 RemoteURI *uri = (RemoteURI *)cur->data;
1925 if (textview->cur_pos >= uri->start &&
1926 textview->cur_pos <= uri->end) {
1929 trimmed_uri = trim_string(uri->uri, 60);
1930 /* single click: display url in statusbar */
1931 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1932 if (textview->messageview->mainwin) {
1933 if (textview->show_url_msgid) {
1934 gtk_timeout_remove(textview->show_url_timeout_tag);
1935 gtk_statusbar_remove(GTK_STATUSBAR(
1936 textview->messageview->mainwin->statusbar),
1937 textview->messageview->mainwin->folderview_cid,
1938 textview->show_url_msgid);
1939 textview->show_url_msgid = 0;
1941 textview->show_url_msgid = gtk_statusbar_push(
1942 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1943 textview->messageview->mainwin->folderview_cid,
1945 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1946 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1949 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1950 if (event->button == 3) {
1951 gchar *fromname, *fromaddress;
1954 fromaddress = g_strdup(uri->uri + 7);
1955 /* Hiroyuki: please put this function in utils.c! */
1956 fromname = procheader_get_fromname(fromaddress);
1957 extract_address(fromaddress);
1958 g_message("adding from textview %s <%s>", fromname, fromaddress);
1959 /* Add to address book - Match */
1960 addressbook_add_contact( fromname, fromaddress, NULL );
1962 g_free(fromaddress);
1965 PrefsAccount *account = NULL;
1966 FolderItem *folder_item;
1968 if (textview->messageview && textview->messageview->mainwin
1969 && textview->messageview->mainwin->summaryview
1970 && textview->messageview->mainwin->summaryview->folder_item) {
1971 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1972 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1973 account = account_find_from_id(folder_item->prefs->default_account);
1975 compose_new(account, uri->uri + 7, NULL);
1979 if (!g_strncasecmp(uri->uri, "pgp:", 4)) {
1980 GtkAdjustment *pos = gtk_scrolled_window_get_vadjustment(
1981 GTK_SCROLLED_WINDOW(textview->scrolledwin));
1982 gfloat vpos = pos->value;
1983 mimeview_check_signature(textview->messageview->mimeview);
1984 /* scroll back where we were */
1985 gtk_adjustment_set_value(pos, vpos);
1990 prefs_common.uri_cmd);
1992 g_free(trimmed_uri);
1997 textview->last_buttonpress = event->type;
2001 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2002 GdkEvent *event, GtkTextIter *iter,
2005 GtkTextIter start_iter, end_iter;
2006 gint start_pos, end_pos;
2007 GdkEventButton *bevent;
2009 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS)
2012 bevent = (GdkEventButton *) event;
2015 ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) ||
2016 bevent->button == 2)) {
2021 if(!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) {
2022 debug_print("Can't find start.");
2025 start_pos = gtk_text_iter_get_offset(&start_iter);
2028 if(!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) {
2029 debug_print("Can't find end");
2032 end_pos = gtk_text_iter_get_offset(&end_iter);
2034 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2035 RemoteURI *uri = (RemoteURI *)cur->data;
2037 if (start_pos == uri->start &&
2038 end_pos == uri->end) {
2039 if (!g_strncasecmp(uri->uri, "mailto:", 7))
2040 compose_new(NULL, uri->uri + 7, NULL);
2043 prefs_common.uri_cmd);
2053 static void textview_uri_list_remove_all(GSList *uri_list)
2057 for (cur = uri_list; cur != NULL; cur = cur->next) {
2059 g_free(((RemoteURI *)cur->data)->uri);
2064 g_slist_free(uri_list);