2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2005 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.
27 #include <glib/gi18n.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <gtk/gtkvbox.h>
31 #include <gtk/gtkscrolledwindow.h>
32 #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"
53 #include "alertpanel.h"
55 #include "image_viewer.h"
68 static GdkColor quote_colors[3] = {
69 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
70 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
71 {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
74 static GdkColor signature_color = {
81 static GdkColor uri_color = {
88 static GdkColor emphasis_color = {
96 static GdkColor error_color = {
105 static GdkCursor *hand_cursor = NULL;
106 static GdkCursor *text_cursor = NULL;
108 #define TEXTVIEW_STATUSBAR_PUSH(textview, str) \
110 gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
111 textview->messageview->statusbar_cid, str); \
114 #define TEXTVIEW_STATUSBAR_POP(textview) \
116 gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
117 textview->messageview->statusbar_cid); \
120 static void textview_show_ertf (TextView *textview,
122 CodeConverter *conv);
123 static void textview_add_part (TextView *textview,
125 static void textview_add_parts (TextView *textview,
127 static void textview_write_body (TextView *textview,
129 static void textview_show_html (TextView *textview,
131 CodeConverter *conv);
133 static void textview_write_line (TextView *textview,
135 CodeConverter *conv);
136 static void textview_write_link (TextView *textview,
139 CodeConverter *conv);
141 static GPtrArray *textview_scan_header (TextView *textview,
143 static void textview_show_header (TextView *textview,
146 static gint textview_key_pressed (GtkWidget *widget,
149 static gboolean textview_motion_notify (GtkWidget *widget,
150 GdkEventMotion *motion,
152 static gboolean textview_leave_notify (GtkWidget *widget,
153 GdkEventCrossing *event,
155 static gboolean textview_visibility_notify (GtkWidget *widget,
156 GdkEventVisibility *event,
158 static void textview_uri_update (TextView *textview,
161 static gboolean textview_get_uri_range (TextView *textview,
164 GtkTextIter *start_iter,
165 GtkTextIter *end_iter);
166 static RemoteURI *textview_get_uri_from_range (TextView *textview,
169 GtkTextIter *start_iter,
170 GtkTextIter *end_iter);
171 static RemoteURI *textview_get_uri (TextView *textview,
174 static gboolean textview_uri_button_pressed (GtkTextTag *tag,
180 static void textview_smooth_scroll_do (TextView *textview,
184 static void textview_smooth_scroll_one_line (TextView *textview,
186 static gboolean textview_smooth_scroll_page (TextView *textview,
189 static gboolean textview_uri_security_check (TextView *textview,
191 static void textview_uri_list_remove_all (GSList *uri_list);
193 static void open_uri_cb (TextView *textview,
196 static void copy_uri_cb (TextView *textview,
199 static void add_uri_to_addrbook_cb (TextView *textview,
202 static void mail_to_uri_cb (TextView *textview,
205 static void copy_mail_to_uri_cb (TextView *textview,
208 static void save_file_cb (TextView *textview,
212 static GtkItemFactoryEntry textview_link_popup_entries[] =
214 {N_("/_Open link"), NULL, open_uri_cb, 0, NULL},
215 {N_("/_Copy link location"), NULL, copy_uri_cb, 0, NULL},
218 static GtkItemFactoryEntry textview_mail_popup_entries[] =
220 {N_("/_Add to addressbook"), NULL, add_uri_to_addrbook_cb, 0, NULL},
221 {N_("/_Email"), NULL, mail_to_uri_cb, 0, NULL},
222 {N_("/_Copy"), NULL, copy_mail_to_uri_cb, 0, NULL},
225 static GtkItemFactoryEntry textview_file_popup_entries[] =
227 {N_("/_Save this image..."), NULL, save_file_cb, 0, NULL},
231 TextView *textview_create(void)
235 GtkWidget *scrolledwin;
237 GtkTextBuffer *buffer;
238 GtkClipboard *clipboard;
239 GtkItemFactory *link_popupfactory, *mail_popupfactory, *file_popupfactory;
240 GtkWidget *link_popupmenu, *mail_popupmenu, *file_popupmenu;
243 debug_print("Creating text view...\n");
244 textview = g_new0(TextView, 1);
246 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
247 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
248 GTK_POLICY_AUTOMATIC,
249 GTK_POLICY_AUTOMATIC);
250 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
252 gtk_widget_set_size_request
253 (scrolledwin, prefs_common.mainview_width, -1);
255 /* create GtkSText widgets for single-byte and multi-byte character */
256 text = gtk_text_view_new();
257 gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
258 gtk_widget_show(text);
259 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
260 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
261 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
262 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
263 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
265 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
266 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
267 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
269 gtk_widget_ensure_style(text);
271 gtk_widget_ref(scrolledwin);
273 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
275 g_signal_connect(G_OBJECT(text), "key-press-event",
276 G_CALLBACK(textview_key_pressed), textview);
277 g_signal_connect(G_OBJECT(text), "motion-notify-event",
278 G_CALLBACK(textview_motion_notify), textview);
279 g_signal_connect(G_OBJECT(text), "leave-notify-event",
280 G_CALLBACK(textview_leave_notify), textview);
281 g_signal_connect(G_OBJECT(text), "visibility-notify-event",
282 G_CALLBACK(textview_visibility_notify), textview);
284 gtk_widget_show(scrolledwin);
286 vbox = gtk_vbox_new(FALSE, 0);
287 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
289 gtk_widget_show(vbox);
291 n_entries = sizeof(textview_link_popup_entries) /
292 sizeof(textview_link_popup_entries[0]);
293 link_popupmenu = menu_create_items(textview_link_popup_entries, n_entries,
294 "<UriPopupMenu>", &link_popupfactory,
297 n_entries = sizeof(textview_mail_popup_entries) /
298 sizeof(textview_mail_popup_entries[0]);
299 mail_popupmenu = menu_create_items(textview_mail_popup_entries, n_entries,
300 "<UriPopupMenu>", &mail_popupfactory,
303 n_entries = sizeof(textview_file_popup_entries) /
304 sizeof(textview_file_popup_entries[0]);
305 file_popupmenu = menu_create_items(textview_file_popup_entries, n_entries,
306 "<FilePopupMenu>", &file_popupfactory,
309 textview->vbox = vbox;
310 textview->scrolledwin = scrolledwin;
311 textview->text = text;
312 textview->uri_list = NULL;
313 textview->body_pos = 0;
314 textview->show_all_headers = FALSE;
315 textview->last_buttonpress = GDK_NOTHING;
316 textview->link_popup_menu = link_popupmenu;
317 textview->link_popup_factory = link_popupfactory;
318 textview->mail_popup_menu = mail_popupmenu;
319 textview->mail_popup_factory = mail_popupfactory;
320 textview->file_popup_menu = file_popupmenu;
321 textview->file_popup_factory = file_popupfactory;
326 static void textview_create_tags(GtkTextView *text, TextView *textview)
328 GtkTextBuffer *buffer;
330 static PangoFontDescription *font_desc, *bold_font_desc;
333 font_desc = pango_font_description_from_string
336 if (!bold_font_desc) {
337 bold_font_desc = pango_font_description_from_string
339 pango_font_description_set_weight
340 (bold_font_desc, PANGO_WEIGHT_BOLD);
343 buffer = gtk_text_view_get_buffer(text);
345 gtk_text_buffer_create_tag(buffer, "header",
346 "pixels-above-lines", 0,
347 "pixels-above-lines-set", TRUE,
348 "pixels-below-lines", 0,
349 "pixels-below-lines-set", TRUE,
350 "font-desc", font_desc,
352 "left-margin-set", TRUE,
354 gtk_text_buffer_create_tag(buffer, "header_title",
355 "font-desc", bold_font_desc,
357 gtk_text_buffer_create_tag(buffer, "quote0",
358 "foreground-gdk", "e_colors[0],
360 gtk_text_buffer_create_tag(buffer, "quote1",
361 "foreground-gdk", "e_colors[1],
363 gtk_text_buffer_create_tag(buffer, "quote2",
364 "foreground-gdk", "e_colors[2],
366 gtk_text_buffer_create_tag(buffer, "emphasis",
367 "foreground-gdk", &emphasis_color,
369 gtk_text_buffer_create_tag(buffer, "signature",
370 "foreground-gdk", &signature_color,
372 tag = gtk_text_buffer_create_tag(buffer, "link",
373 "foreground-gdk", &uri_color,
375 gtk_text_buffer_create_tag(buffer, "link-hover",
376 "foreground-gdk", &uri_color,
377 "underline", PANGO_UNDERLINE_SINGLE,
380 g_signal_connect(G_OBJECT(tag), "event",
381 G_CALLBACK(textview_uri_button_pressed), textview);
384 void textview_init(TextView *textview)
387 hand_cursor = gdk_cursor_new(GDK_HAND2);
389 text_cursor = gdk_cursor_new(GDK_XTERM);
391 textview_update_message_colors();
392 textview_set_all_headers(textview, FALSE);
393 textview_set_font(textview, NULL);
394 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
397 void textview_update_message_colors(void)
399 GdkColor black = {0, 0, 0, 0};
401 if (prefs_common.enable_color) {
402 /* grab the quote colors, converting from an int to a GdkColor */
403 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
405 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
407 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
409 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
411 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
414 quote_colors[0] = quote_colors[1] = quote_colors[2] =
415 uri_color = emphasis_color = signature_color = black;
419 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
424 if ((fp = fopen(file, "rb")) == NULL) {
425 FILE_OP_ERROR(file, "fopen");
429 textview_clear(textview);
431 textview_add_parts(textview, mimeinfo);
435 textview_set_position(textview, 0);
438 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
442 g_return_if_fail(mimeinfo != NULL);
443 g_return_if_fail(fp != NULL);
445 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
446 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
447 textview_clear(textview);
448 textview_add_parts(textview, mimeinfo);
452 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
455 textview_clear(textview);
457 if (mimeinfo->type == MIMETYPE_MULTIPART)
458 textview_add_parts(textview, mimeinfo);
460 textview_write_body(textview, mimeinfo);
464 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
467 GtkTextBuffer *buffer;
468 GtkTextIter iter, start_iter;
470 GPtrArray *headers = NULL;
475 g_return_if_fail(mimeinfo != NULL);
476 text = GTK_TEXT_VIEW(textview->text);
477 buffer = gtk_text_view_get_buffer(text);
478 charcount = gtk_text_buffer_get_char_count(buffer);
479 gtk_text_buffer_get_end_iter(buffer, &iter);
481 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
483 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
486 fp = fopen(mimeinfo->data.filename, "rb");
487 fseek(fp, mimeinfo->offset, SEEK_SET);
488 headers = textview_scan_header(textview, fp);
491 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
492 textview_show_header(textview, headers);
493 procheader_header_array_destroy(headers);
499 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
500 content_type = procmime_get_content_type_str(mimeinfo->type,
503 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
505 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
506 name, content_type, mimeinfo->length);
508 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
509 content_type, mimeinfo->length);
511 g_free(content_type);
513 if (mimeinfo->type != MIMETYPE_TEXT) {
514 gtk_text_buffer_insert(buffer, &iter, buf, -1);
515 if (mimeinfo->type == MIMETYPE_IMAGE &&
516 prefs_common.inline_img ) {
518 GError *error = NULL;
524 fp = fopen(mimeinfo->data.filename, "rb");
525 fseek(fp, mimeinfo->offset, SEEK_SET);
527 filename = procmime_get_tmp_file_name(mimeinfo);
528 if (procmime_get_part(filename, mimeinfo) < 0) {
529 g_warning("Can't get the image file.");
534 pixbuf = gdk_pixbuf_new_from_file(filename, &error);
536 g_warning("%s\n", error->message);
540 g_warning("Can't load the image.");
545 if (prefs_common.resize_img) {
546 int new_width, new_height;
548 image_viewer_get_resized_size(gdk_pixbuf_get_width(pixbuf),
549 gdk_pixbuf_get_height(pixbuf),
550 textview->scrolledwin->allocation.width - 100,
551 gdk_pixbuf_get_height(pixbuf),
552 &new_width, &new_height);
553 scaled = gdk_pixbuf_scale_simple
554 (pixbuf, new_width, new_height, GDK_INTERP_BILINEAR);
556 g_object_unref(pixbuf);
560 uri_str = g_filename_to_uri(filename, NULL, NULL);
562 uri = g_new(RemoteURI, 1);
564 uri->start = gtk_text_iter_get_offset(&iter);
566 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf);
568 uri->end = uri->start + 1;
569 uri->filename = procmime_get_part_file_name(mimeinfo);
571 g_slist_append(textview->uri_list, uri);
573 gtk_text_buffer_insert(buffer, &iter, " ", 1);
574 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, uri->start);
575 gtk_text_buffer_apply_tag_by_name(buffer, "link",
578 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf);
579 gtk_text_buffer_insert(buffer, &iter, " ", 1);
582 g_object_unref(pixbuf);
585 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
586 if (prefs_common.display_header && (charcount > 0))
587 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
589 textview_write_body(textview, mimeinfo);
593 static void recursive_add_parts(TextView *textview, GNode *node)
598 mimeinfo = (MimeInfo *) node->data;
600 textview_add_part(textview, mimeinfo);
602 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
603 (mimeinfo->type != MIMETYPE_MESSAGE))
606 if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
607 GNode * prefered_body;
615 prefered_body = NULL;
618 for (iter = g_node_first_child(node) ; iter != NULL ;
619 iter = g_node_next_sibling(iter)) {
624 submime = (MimeInfo *) iter->data;
625 if (submime->type == MIMETYPE_TEXT)
628 if (submime->subtype != NULL) {
629 if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
633 if (score > prefered_score) {
634 prefered_score = score;
635 prefered_body = iter;
639 if (prefered_body != NULL) {
640 recursive_add_parts(textview, prefered_body);
644 for (iter = g_node_first_child(node) ; iter != NULL ;
645 iter = g_node_next_sibling(iter)) {
646 recursive_add_parts(textview, iter);
651 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
653 g_return_if_fail(mimeinfo != NULL);
655 recursive_add_parts(textview, mimeinfo->node);
658 #define TEXT_INSERT(str) \
659 gtk_text_buffer_insert(buffer, &iter, str, -1)
661 void textview_show_error(TextView *textview)
664 GtkTextBuffer *buffer;
667 textview_set_font(textview, NULL);
668 textview_clear(textview);
670 text = GTK_TEXT_VIEW(textview->text);
671 buffer = gtk_text_view_get_buffer(text);
672 gtk_text_buffer_get_start_iter(buffer, &iter);
674 TEXT_INSERT(_("This message can't be displayed.\n"));
678 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
681 GtkTextBuffer *buffer;
684 if (!partinfo) return;
686 textview_set_font(textview, NULL);
687 textview_clear(textview);
689 text = GTK_TEXT_VIEW(textview->text);
690 buffer = gtk_text_view_get_buffer(text);
691 gtk_text_buffer_get_start_iter(buffer, &iter);
693 TEXT_INSERT(_("The following can be performed on this part by "));
694 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
696 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
697 TEXT_INSERT(_(" To display as text select 'Display as text' "));
698 TEXT_INSERT(_("(Shortcut key: 't')\n"));
699 TEXT_INSERT(_(" To open with an external program select 'Open' "));
700 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
701 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
702 TEXT_INSERT(_("mouse button),\n"));
703 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
709 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
714 const gchar *charset;
716 if (textview->messageview->forced_charset)
717 charset = textview->messageview->forced_charset;
719 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
721 textview_set_font(textview, charset);
723 conv = conv_code_converter_new(charset);
725 procmime_force_encoding(textview->messageview->forced_encoding);
727 textview->is_in_signature = FALSE;
729 procmime_decode_content(mimeinfo);
731 if (!g_ascii_strcasecmp(mimeinfo->subtype, "html") &&
732 prefs_common.render_html) {
735 filename = procmime_get_tmp_file_name(mimeinfo);
736 if (procmime_get_part(filename, mimeinfo) == 0) {
737 tmpfp = fopen(filename, "rb");
738 textview_show_html(textview, tmpfp, conv);
743 } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
746 filename = procmime_get_tmp_file_name(mimeinfo);
747 if (procmime_get_part(filename, mimeinfo) == 0) {
748 tmpfp = fopen(filename, "rb");
749 textview_show_ertf(textview, tmpfp, conv);
755 tmpfp = fopen(mimeinfo->data.filename, "rb");
756 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
757 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
758 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
759 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
760 textview_write_line(textview, buf, conv);
764 conv_code_converter_destroy(conv);
765 procmime_force_encoding(0);
768 static void textview_show_html(TextView *textview, FILE *fp,
774 parser = html_parser_new(fp, conv);
775 g_return_if_fail(parser != NULL);
777 while ((str = html_parse(parser)) != NULL) {
778 if (parser->state == HTML_HREF) {
779 /* first time : get and copy the URL */
780 if (parser->href == NULL) {
781 /* ALF - the sylpheed html parser returns an empty string,
782 * if still inside an <a>, but already parsed past HREF */
783 str = strtok(str, " ");
785 parser->href = g_strdup(str);
786 /* the URL may (or not) be followed by the
788 str = strtok(NULL, "");
792 textview_write_link(textview, str, parser->href, NULL);
794 textview_write_line(textview, str, NULL);
796 textview_write_line(textview, "\n", NULL);
797 html_parser_destroy(parser);
800 static void textview_show_ertf(TextView *textview, FILE *fp,
806 parser = ertf_parser_new(fp, conv);
807 g_return_if_fail(parser != NULL);
809 while ((str = ertf_parse(parser)) != NULL) {
810 textview_write_line(textview, str, NULL);
813 ertf_parser_destroy(parser);
816 /* get_uri_part() - retrieves a URI starting from scanpos.
817 Returns TRUE if succesful */
818 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
819 const gchar **bp, const gchar **ep)
823 g_return_val_if_fail(start != NULL, FALSE);
824 g_return_val_if_fail(scanpos != NULL, FALSE);
825 g_return_val_if_fail(bp != NULL, FALSE);
826 g_return_val_if_fail(ep != NULL, FALSE);
830 /* find end point of URI */
831 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
832 if (!isgraph(*(const guchar *)ep_) ||
833 !IS_ASCII(*(const guchar *)ep_) ||
834 strchr("()<>\"", *ep_))
838 /* no punctuation at end of string */
840 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
841 * should pass some URI type to this function and decide on that whether
842 * to perform punctuation stripping */
844 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
846 for (; ep_ - 1 > scanpos + 1 &&
847 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
858 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
860 return g_strndup(bp, ep - bp);
863 /* valid mail address characters */
864 #define IS_RFC822_CHAR(ch) \
869 !strchr("(),;<>\"", (ch)))
871 /* alphabet and number within 7bit ASCII */
872 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && isalnum(ch))
873 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
875 static GHashTable *create_domain_tab(void)
877 static const gchar *toplvl_domains [] = {
879 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
880 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
881 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
882 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
883 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
884 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
885 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
886 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
887 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
888 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
889 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
890 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
891 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
892 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
893 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
894 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
895 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
896 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
897 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
898 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
899 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
900 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
901 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
902 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
903 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
904 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
907 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
909 g_return_val_if_fail(htab, NULL);
910 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
911 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
915 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
917 const gint MAX_LVL_DOM_NAME_LEN = 6;
918 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
919 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
922 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
925 for (p = buf; p < m && first < last; *p++ = *first++)
929 return g_hash_table_lookup(tab, buf) != NULL;
932 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
933 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
934 const gchar **bp, const gchar **ep)
936 /* more complex than the uri part because we need to scan back and forward starting from
937 * the scan position. */
938 gboolean result = FALSE;
939 const gchar *bp_ = NULL;
940 const gchar *ep_ = NULL;
941 static GHashTable *dom_tab;
942 const gchar *last_dot = NULL;
943 const gchar *prelast_dot = NULL;
944 const gchar *last_tld_char = NULL;
946 /* the informative part of the email address (describing the name
947 * of the email address owner) may contain quoted parts. the
948 * closure stack stores the last encountered quotes. */
949 gchar closure_stack[128];
950 gchar *ptr = closure_stack;
952 g_return_val_if_fail(start != NULL, FALSE);
953 g_return_val_if_fail(scanpos != NULL, FALSE);
954 g_return_val_if_fail(bp != NULL, FALSE);
955 g_return_val_if_fail(ep != NULL, FALSE);
958 dom_tab = create_domain_tab();
959 g_return_val_if_fail(dom_tab, FALSE);
961 /* scan start of address */
962 for (bp_ = scanpos - 1;
963 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
966 /* TODO: should start with an alnum? */
968 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
971 if (bp_ != scanpos) {
972 /* scan end of address */
973 for (ep_ = scanpos + 1;
974 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
976 prelast_dot = last_dot;
978 if (*(last_dot + 1) == '.') {
979 if (prelast_dot == NULL)
981 last_dot = prelast_dot;
986 /* TODO: really should terminate with an alnum? */
987 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
992 if (last_dot == NULL)
995 last_dot = prelast_dot;
996 if (last_dot == NULL || (scanpos + 1 >= last_dot))
1000 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
1001 if (*last_tld_char == '?')
1004 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
1011 if (!result) return FALSE;
1013 if (*ep_ && *(bp_ - 1) == '"' && *(ep_) == '"'
1014 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
1015 && IS_RFC822_CHAR(*(ep_ + 3))) {
1016 /* this informative part with an @ in it is
1017 * followed by the email address */
1020 /* go to matching '>' (or next non-rfc822 char, like \n) */
1021 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
1024 /* include the bracket */
1025 if (*ep_ == '>') ep_++;
1027 /* include the leading quote */
1035 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
1036 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
1039 /* see if this is <bracketed>; in this case we also scan for the informative part. */
1040 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
1043 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
1044 #define IN_STACK() (ptr > closure_stack)
1045 /* has underrun check */
1046 #define POP_STACK() if(IN_STACK()) --ptr
1047 /* has overrun check */
1048 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
1049 /* has underrun check */
1050 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
1054 /* scan for the informative part. */
1055 for (bp_ -= 2; bp_ >= start; bp_--) {
1056 /* if closure on the stack keep scanning */
1057 if (PEEK_STACK() == *bp_) {
1061 if (*bp_ == '\'' || *bp_ == '"') {
1066 /* if nothing in the closure stack, do the special conditions
1067 * the following if..else expression simply checks whether
1068 * a token is acceptable. if not acceptable, the clause
1069 * should terminate the loop with a 'break' */
1070 if (!PEEK_STACK()) {
1072 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
1073 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
1074 /* hyphens are allowed, but only in
1076 } else if (!strchr(",;:=?./+<>!&", *bp_)) {
1077 /* but anything not being a punctiation
1080 break; /* anything else is rejected */
1093 /* scan forward (should start with an alnum) */
1094 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
1104 #undef IS_ASCII_ALNUM
1105 #undef IS_RFC822_CHAR
1107 static gchar *make_email_string(const gchar *bp, const gchar *ep)
1109 /* returns a mailto: URI; mailto: is also used to detect the
1110 * uri type later on in the button_pressed signal handler */
1114 tmp = g_strndup(bp, ep - bp);
1115 result = g_strconcat("mailto:", tmp, NULL);
1121 static gchar *make_http_string(const gchar *bp, const gchar *ep)
1123 /* returns an http: URI; */
1127 tmp = g_strndup(bp, ep - bp);
1128 result = g_strconcat("http://", tmp, NULL);
1134 #define ADD_TXT_POS(bp_, ep_, pti_) \
1135 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1136 last = last->next; \
1137 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1138 last->next = NULL; \
1140 g_warning("alloc error scanning URIs\n"); \
1141 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1147 /* textview_make_clickable_parts() - colorizes clickable parts */
1148 static void textview_make_clickable_parts(TextView *textview,
1149 const gchar *fg_tag,
1150 const gchar *uri_tag,
1151 const gchar *linebuf)
1153 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1154 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1157 /* parse table - in order of priority */
1159 const gchar *needle; /* token */
1161 /* token search function */
1162 gchar *(*search) (const gchar *haystack,
1163 const gchar *needle);
1164 /* part parsing function */
1165 gboolean (*parse) (const gchar *start,
1166 const gchar *scanpos,
1169 /* part to URI function */
1170 gchar *(*build_uri) (const gchar *bp,
1174 static struct table parser[] = {
1175 {"http://", strcasestr, get_uri_part, make_uri_string},
1176 {"https://", strcasestr, get_uri_part, make_uri_string},
1177 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1178 {"www.", strcasestr, get_uri_part, make_http_string},
1179 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1180 {"@", strcasestr, get_email_part, make_email_string}
1182 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1185 const gchar *walk, *bp, *ep;
1188 const gchar *bp, *ep; /* text position */
1189 gint pti; /* index in parse table */
1190 struct txtpos *next; /* next */
1191 } head = {NULL, NULL, 0, NULL}, *last = &head;
1193 gtk_text_buffer_get_end_iter(buffer, &iter);
1195 /* parse for clickable parts, and build a list of begin and end positions */
1196 for (walk = linebuf, n = 0;;) {
1197 gint last_index = PARSE_ELEMS;
1198 gchar *scanpos = NULL;
1200 /* FIXME: this looks phony. scanning for anything in the parse table */
1201 for (n = 0; n < PARSE_ELEMS; n++) {
1204 tmp = parser[n].search(walk, parser[n].needle);
1206 if (scanpos == NULL || tmp < scanpos) {
1214 /* check if URI can be parsed */
1215 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1216 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1217 ADD_TXT_POS(bp, ep, last_index);
1221 strlen(parser[last_index].needle);
1226 /* colorize this line */
1228 const gchar *normal_text = linebuf;
1231 for (last = head.next; last != NULL;
1232 normal_text = last->ep, last = last->next) {
1234 uri = g_new(RemoteURI, 1);
1235 if (last->bp - normal_text > 0)
1236 gtk_text_buffer_insert_with_tags_by_name
1239 last->bp - normal_text,
1241 uri->uri = parser[last->pti].build_uri(last->bp,
1243 uri->start = gtk_text_iter_get_offset(&iter);
1244 gtk_text_buffer_insert_with_tags_by_name
1245 (buffer, &iter, last->bp, last->ep - last->bp,
1246 uri_tag, fg_tag, NULL);
1247 uri->end = gtk_text_iter_get_offset(&iter);
1248 uri->filename = NULL;
1249 textview->uri_list =
1250 g_slist_append(textview->uri_list, uri);
1254 gtk_text_buffer_insert_with_tags_by_name
1255 (buffer, &iter, normal_text, -1, fg_tag, NULL);
1257 gtk_text_buffer_insert_with_tags_by_name
1258 (buffer, &iter, linebuf, -1, fg_tag, NULL);
1264 static void textview_write_line(TextView *textview, const gchar *str,
1265 CodeConverter *conv)
1268 GtkTextBuffer *buffer;
1270 gchar buf[BUFFSIZE];
1272 gint quotelevel = -1;
1273 gchar quote_tag_str[10];
1275 text = GTK_TEXT_VIEW(textview->text);
1276 buffer = gtk_text_view_get_buffer(text);
1277 gtk_text_buffer_get_end_iter(buffer, &iter);
1280 strncpy2(buf, str, sizeof(buf));
1281 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1282 conv_utf8todisp(buf, sizeof(buf), str);
1285 //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1288 /* change color of quotation
1289 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1290 Up to 3 levels of quotations are detected, and each
1291 level is colored using a different color. */
1292 if (prefs_common.enable_color
1293 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1294 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1296 /* set up the correct foreground color */
1297 if (quotelevel > 2) {
1298 /* recycle colors */
1299 if (prefs_common.recycle_quote_colors)
1306 if (quotelevel == -1)
1309 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1310 "quote%d", quotelevel);
1311 fg_color = quote_tag_str;
1314 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1315 fg_color = "signature";
1316 textview->is_in_signature = TRUE;
1319 if (prefs_common.enable_color)
1320 textview_make_clickable_parts(textview, fg_color, "link", buf);
1322 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1325 void textview_write_link(TextView *textview, const gchar *str,
1326 const gchar *uri, CodeConverter *conv)
1328 GdkColor *link_color = NULL;
1330 GtkTextBuffer *buffer;
1332 gchar buf[BUFFSIZE];
1339 text = GTK_TEXT_VIEW(textview->text);
1340 buffer = gtk_text_view_get_buffer(text);
1341 gtk_text_buffer_get_end_iter(buffer, &iter);
1344 strncpy2(buf, str, sizeof(buf));
1345 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1346 conv_utf8todisp(buf, sizeof(buf), str);
1350 gtk_text_buffer_get_end_iter(buffer, &iter);
1352 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1353 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1355 if (prefs_common.enable_color) {
1356 link_color = &uri_color;
1358 r_uri = g_new(RemoteURI, 1);
1359 r_uri->uri = g_strdup(uri);
1360 r_uri->start = gtk_text_iter_get_offset(&iter);
1361 gtk_text_buffer_insert_with_tags_by_name
1362 (buffer, &iter, bufp, -1, "link", NULL);
1363 r_uri->end = gtk_text_iter_get_offset(&iter);
1364 r_uri->filename = NULL;
1365 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1368 void textview_clear(TextView *textview)
1370 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1371 GtkTextBuffer *buffer;
1373 buffer = gtk_text_view_get_buffer(text);
1374 gtk_text_buffer_set_text(buffer, "", -1);
1376 TEXTVIEW_STATUSBAR_POP(textview);
1377 textview_uri_list_remove_all(textview->uri_list);
1378 textview->uri_list = NULL;
1380 textview->body_pos = 0;
1383 void textview_destroy(TextView *textview)
1385 textview_uri_list_remove_all(textview->uri_list);
1386 textview->uri_list = NULL;
1391 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1393 textview->show_all_headers = all_headers;
1396 void textview_set_font(TextView *textview, const gchar *codeset)
1398 if (prefs_common.textfont) {
1399 PangoFontDescription *font_desc = NULL;
1401 font_desc = pango_font_description_from_string
1402 (prefs_common.textfont);
1404 gtk_widget_modify_font(textview->text, font_desc);
1405 pango_font_description_free(font_desc);
1408 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1409 prefs_common.line_space / 2);
1410 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1411 prefs_common.line_space / 2);
1414 void textview_set_text(TextView *textview, const gchar *text)
1417 GtkTextBuffer *buffer;
1419 g_return_if_fail(textview != NULL);
1420 g_return_if_fail(text != NULL);
1422 textview_clear(textview);
1424 view = GTK_TEXT_VIEW(textview->text);
1425 buffer = gtk_text_view_get_buffer(view);
1426 gtk_text_buffer_set_text(buffer, text, strlen(text));
1442 H_ORGANIZATION = 11,
1445 void textview_set_position(TextView *textview, gint pos)
1447 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1448 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1451 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1452 gtk_text_buffer_place_cursor(buffer, &iter);
1453 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1456 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1458 gchar buf[BUFFSIZE];
1459 GPtrArray *headers, *sorted_headers;
1460 GSList *disphdr_list;
1464 g_return_val_if_fail(fp != NULL, NULL);
1466 if (textview->show_all_headers)
1467 return procheader_get_header_array_asis(fp);
1469 if (!prefs_common.display_header) {
1470 while (fgets(buf, sizeof(buf), fp) != NULL)
1471 if (buf[0] == '\r' || buf[0] == '\n') break;
1475 headers = procheader_get_header_array_asis(fp);
1477 sorted_headers = g_ptr_array_new();
1479 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1480 disphdr_list = disphdr_list->next) {
1481 DisplayHeaderProp *dp =
1482 (DisplayHeaderProp *)disphdr_list->data;
1484 for (i = 0; i < headers->len; i++) {
1485 header = g_ptr_array_index(headers, i);
1487 if (procheader_headername_equal(header->name,
1490 procheader_header_free(header);
1492 g_ptr_array_add(sorted_headers, header);
1494 g_ptr_array_remove_index(headers, i);
1500 if (prefs_common.show_other_header) {
1501 for (i = 0; i < headers->len; i++) {
1502 header = g_ptr_array_index(headers, i);
1503 g_ptr_array_add(sorted_headers, header);
1505 g_ptr_array_free(headers, TRUE);
1507 procheader_header_array_destroy(headers);
1510 return sorted_headers;
1513 static void textview_show_header(TextView *textview, GPtrArray *headers)
1515 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1516 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1521 g_return_if_fail(headers != NULL);
1523 for (i = 0; i < headers->len; i++) {
1524 header = g_ptr_array_index(headers, i);
1525 g_return_if_fail(header->name != NULL);
1527 gtk_text_buffer_get_end_iter (buffer, &iter);
1528 gtk_text_buffer_insert_with_tags_by_name
1529 (buffer, &iter, header->name, -1,
1530 "header_title", "header", NULL);
1531 if (header->name[strlen(header->name) - 1] != ' ')
1532 gtk_text_buffer_insert_with_tags_by_name
1533 (buffer, &iter, " ", 1,
1534 "header_title", "header", NULL);
1536 if (procheader_headername_equal(header->name, "Subject") ||
1537 procheader_headername_equal(header->name, "From") ||
1538 procheader_headername_equal(header->name, "To") ||
1539 procheader_headername_equal(header->name, "Cc"))
1540 unfold_line(header->body);
1542 if (prefs_common.enable_color &&
1543 (procheader_headername_equal(header->name, "X-Mailer") ||
1544 procheader_headername_equal(header->name,
1546 strstr(header->body, "Sylpheed") != NULL) {
1547 gtk_text_buffer_get_end_iter (buffer, &iter);
1548 gtk_text_buffer_insert_with_tags_by_name
1549 (buffer, &iter, header->body, -1,
1550 "header", "emphasis", NULL);
1551 } else if (prefs_common.enable_color) {
1552 textview_make_clickable_parts(textview, "header", "link",
1555 textview_make_clickable_parts(textview, "header", NULL,
1558 gtk_text_buffer_get_end_iter (buffer, &iter);
1559 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1564 gboolean textview_search_string(TextView *textview, const gchar *str,
1567 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1568 GtkTextBuffer *buffer;
1569 GtkTextIter iter, match_pos;
1573 g_return_val_if_fail(str != NULL, FALSE);
1575 buffer = gtk_text_view_get_buffer(text);
1577 len = g_utf8_strlen(str, -1);
1578 g_return_val_if_fail(len >= 0, FALSE);
1580 mark = gtk_text_buffer_get_insert(buffer);
1581 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1583 if (gtkut_text_buffer_find(buffer, &iter, str, case_sens,
1585 GtkTextIter end = match_pos;
1587 gtk_text_iter_forward_chars(&end, len);
1588 /* place "insert" at the last character */
1589 gtk_text_buffer_select_range(buffer, &end, &match_pos);
1590 gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1597 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1600 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1601 GtkTextBuffer *buffer;
1602 GtkTextIter iter, match_pos;
1606 g_return_val_if_fail(str != NULL, FALSE);
1608 buffer = gtk_text_view_get_buffer(text);
1610 len = g_utf8_strlen(str, -1);
1611 g_return_val_if_fail(len >= 0, FALSE);
1613 mark = gtk_text_buffer_get_insert(buffer);
1614 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1616 if (gtkut_text_buffer_find_backward(buffer, &iter, str, case_sens,
1618 GtkTextIter end = match_pos;
1620 gtk_text_iter_forward_chars(&end, len);
1621 gtk_text_buffer_select_range(buffer, &match_pos, &end);
1622 gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1629 void textview_scroll_one_line(TextView *textview, gboolean up)
1631 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1632 GtkAdjustment *vadj = text->vadjustment;
1635 if (prefs_common.enable_smooth_scroll) {
1636 textview_smooth_scroll_one_line(textview, up);
1641 upper = vadj->upper - vadj->page_size;
1642 if (vadj->value < upper) {
1643 vadj->value += vadj->step_increment;
1644 vadj->value = MIN(vadj->value, upper);
1645 g_signal_emit_by_name(G_OBJECT(vadj),
1646 "value_changed", 0);
1649 if (vadj->value > 0.0) {
1650 vadj->value -= vadj->step_increment;
1651 vadj->value = MAX(vadj->value, 0.0);
1652 g_signal_emit_by_name(G_OBJECT(vadj),
1653 "value_changed", 0);
1658 gboolean textview_scroll_page(TextView *textview, gboolean up)
1660 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1661 GtkAdjustment *vadj = text->vadjustment;
1665 if (prefs_common.enable_smooth_scroll)
1666 return textview_smooth_scroll_page(textview, up);
1668 if (prefs_common.scroll_halfpage)
1669 page_incr = vadj->page_increment / 2;
1671 page_incr = vadj->page_increment;
1674 upper = vadj->upper - vadj->page_size;
1675 if (vadj->value < upper) {
1676 vadj->value += page_incr;
1677 vadj->value = MIN(vadj->value, upper);
1678 g_signal_emit_by_name(G_OBJECT(vadj),
1679 "value_changed", 0);
1683 if (vadj->value > 0.0) {
1684 vadj->value -= page_incr;
1685 vadj->value = MAX(vadj->value, 0.0);
1686 g_signal_emit_by_name(G_OBJECT(vadj),
1687 "value_changed", 0);
1695 static void textview_smooth_scroll_do(TextView *textview,
1696 gfloat old_value, gfloat last_value,
1699 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1700 GtkAdjustment *vadj = text->vadjustment;
1705 if (old_value < last_value) {
1706 change_value = last_value - old_value;
1709 change_value = old_value - last_value;
1713 for (i = step; i <= change_value; i += step) {
1714 vadj->value = old_value + (up ? -i : i);
1715 g_signal_emit_by_name(G_OBJECT(vadj),
1716 "value_changed", 0);
1719 vadj->value = last_value;
1720 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1723 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1725 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1726 GtkAdjustment *vadj = text->vadjustment;
1732 upper = vadj->upper - vadj->page_size;
1733 if (vadj->value < upper) {
1734 old_value = vadj->value;
1735 last_value = vadj->value + vadj->step_increment;
1736 last_value = MIN(last_value, upper);
1738 textview_smooth_scroll_do(textview, old_value,
1740 prefs_common.scroll_step);
1743 if (vadj->value > 0.0) {
1744 old_value = vadj->value;
1745 last_value = vadj->value - vadj->step_increment;
1746 last_value = MAX(last_value, 0.0);
1748 textview_smooth_scroll_do(textview, old_value,
1750 prefs_common.scroll_step);
1755 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1757 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1758 GtkAdjustment *vadj = text->vadjustment;
1764 if (prefs_common.scroll_halfpage)
1765 page_incr = vadj->page_increment / 2;
1767 page_incr = vadj->page_increment;
1770 upper = vadj->upper - vadj->page_size;
1771 if (vadj->value < upper) {
1772 old_value = vadj->value;
1773 last_value = vadj->value + page_incr;
1774 last_value = MIN(last_value, upper);
1776 textview_smooth_scroll_do(textview, old_value,
1778 prefs_common.scroll_step);
1782 if (vadj->value > 0.0) {
1783 old_value = vadj->value;
1784 last_value = vadj->value - page_incr;
1785 last_value = MAX(last_value, 0.0);
1787 textview_smooth_scroll_do(textview, old_value,
1789 prefs_common.scroll_step);
1797 #define KEY_PRESS_EVENT_STOP() \
1798 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1801 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1804 SummaryView *summaryview = NULL;
1805 MessageView *messageview = textview->messageview;
1807 if (!event) return FALSE;
1808 if (messageview->mainwin)
1809 summaryview = messageview->mainwin->summaryview;
1811 switch (event->keyval) {
1826 summary_pass_key_press_event(summaryview, event);
1828 textview_scroll_page
1831 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1834 textview_scroll_page(textview, TRUE);
1837 textview_scroll_one_line
1838 (textview, (event->state &
1839 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1843 summary_pass_key_press_event(summaryview, event);
1848 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1849 KEY_PRESS_EVENT_STOP();
1850 mimeview_pass_key_press_event(messageview->mimeview,
1854 /* possible fall through */
1857 event->window != messageview->mainwin->window->window) {
1858 GdkEventKey tmpev = *event;
1860 tmpev.window = messageview->mainwin->window->window;
1861 KEY_PRESS_EVENT_STOP();
1862 gtk_widget_event(messageview->mainwin->window,
1863 (GdkEvent *)&tmpev);
1871 static gboolean textview_motion_notify(GtkWidget *widget,
1872 GdkEventMotion *event,
1875 textview_uri_update(textview, event->x, event->y);
1876 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1881 static gboolean textview_leave_notify(GtkWidget *widget,
1882 GdkEventCrossing *event,
1885 textview_uri_update(textview, -1, -1);
1890 static gboolean textview_visibility_notify(GtkWidget *widget,
1891 GdkEventVisibility *event,
1897 window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1898 GTK_TEXT_WINDOW_TEXT);
1900 /* check if occurred for the text window part */
1901 if (window != event->window)
1904 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1905 textview_uri_update(textview, wx, wy);
1910 static void textview_uri_update(TextView *textview, gint x, gint y)
1912 GtkTextBuffer *buffer;
1913 GtkTextIter start_iter, end_iter;
1914 RemoteURI *uri = NULL;
1916 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1918 if (x != -1 && y != -1) {
1924 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1925 GTK_TEXT_WINDOW_WIDGET,
1927 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1930 tags = gtk_text_iter_get_tags(&iter);
1931 for (cur = tags; cur != NULL; cur = cur->next) {
1932 GtkTextTag *tag = cur->data;
1935 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1936 if (!strcmp(name, "link")
1937 && textview_get_uri_range(textview, &iter, tag,
1938 &start_iter, &end_iter))
1939 uri = textview_get_uri_from_range(textview,
1951 if (uri != textview->uri_hover) {
1954 if (textview->uri_hover)
1955 gtk_text_buffer_remove_tag_by_name(buffer,
1957 &textview->uri_hover_start_iter,
1958 &textview->uri_hover_end_iter);
1960 textview->uri_hover = uri;
1962 textview->uri_hover_start_iter = start_iter;
1963 textview->uri_hover_end_iter = end_iter;
1966 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1967 GTK_TEXT_WINDOW_TEXT);
1968 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1970 TEXTVIEW_STATUSBAR_POP(textview);
1975 gtk_text_buffer_apply_tag_by_name(buffer,
1980 trimmed_uri = trim_string(uri->uri, 60);
1981 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1982 g_free(trimmed_uri);
1987 static gboolean textview_get_uri_range(TextView *textview,
1990 GtkTextIter *start_iter,
1991 GtkTextIter *end_iter)
1993 GtkTextIter _start_iter, _end_iter;
1996 if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1997 debug_print("Can't find end");
2001 _start_iter = _end_iter;
2002 if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
2003 debug_print("Can't find start.");
2007 *start_iter = _start_iter;
2008 *end_iter = _end_iter;
2013 static RemoteURI *textview_get_uri_from_range(TextView *textview,
2016 GtkTextIter *start_iter,
2017 GtkTextIter *end_iter)
2019 gint start_pos, end_pos, cur_pos;
2020 RemoteURI *uri = NULL;
2023 start_pos = gtk_text_iter_get_offset(start_iter);
2024 end_pos = gtk_text_iter_get_offset(end_iter);
2025 cur_pos = gtk_text_iter_get_offset(iter);
2027 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2028 RemoteURI *uri_ = (RemoteURI *)cur->data;
2029 if (start_pos == uri_->start &&
2030 end_pos == uri_->end) {
2033 } else if (start_pos == uri_->start ||
2034 end_pos == uri_->end) {
2035 /* in case of contiguous links, textview_get_uri_range
2036 * returns a broader range (start of 1st link to end
2038 * In that case, correct link is the one covering
2041 if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
2051 static RemoteURI *textview_get_uri(TextView *textview,
2055 GtkTextIter start_iter, end_iter;
2056 RemoteURI *uri = NULL;
2058 if (textview_get_uri_range(textview, iter, tag, &start_iter,
2060 uri = textview_get_uri_from_range(textview, iter, tag,
2061 &start_iter, &end_iter);
2066 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2067 GdkEvent *event, GtkTextIter *iter,
2070 GdkEventButton *bevent;
2071 RemoteURI *uri = NULL;
2078 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2079 && event->type != GDK_MOTION_NOTIFY)
2082 uri = textview_get_uri(textview, iter, tag);
2086 bevent = (GdkEventButton *) event;
2088 /* doubleclick: open compose / add address / browser */
2089 if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2090 bevent->button == 2 || bevent->button == 3) {
2091 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2092 if (bevent->button == 3) {
2094 G_OBJECT(textview->mail_popup_menu),
2095 "menu_button", uri);
2096 gtk_menu_popup(GTK_MENU(textview->mail_popup_menu),
2097 NULL, NULL, NULL, NULL,
2098 bevent->button, bevent->time);
2100 PrefsAccount *account = NULL;
2102 if (textview->messageview && textview->messageview->msginfo &&
2103 textview->messageview->msginfo->folder) {
2104 FolderItem *folder_item;
2106 folder_item = textview->messageview->msginfo->folder;
2107 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2108 account = account_find_from_id(folder_item->prefs->default_account);
2110 compose_new(account, uri->uri + 7, NULL);
2113 } else if (g_ascii_strncasecmp(uri->uri, "file:", 5)) {
2114 if (bevent->button == 1 &&
2115 textview_uri_security_check(textview, uri) == TRUE)
2117 prefs_common.uri_cmd);
2118 else if (bevent->button == 3) {
2120 G_OBJECT(textview->link_popup_menu),
2121 "menu_button", uri);
2122 gtk_menu_popup(GTK_MENU(textview->link_popup_menu),
2123 NULL, NULL, NULL, NULL,
2124 bevent->button, bevent->time);
2128 if (bevent->button == 3) {
2130 G_OBJECT(textview->file_popup_menu),
2131 "menu_button", uri);
2132 gtk_menu_popup(GTK_MENU(textview->file_popup_menu),
2133 NULL, NULL, NULL, NULL,
2134 bevent->button, bevent->time);
2144 *\brief Check to see if a web URL has been disguised as a different
2145 * URL (possible with HTML email).
2147 *\param uri The uri to check
2149 *\param textview The TextView the URL is contained in
2151 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2152 * it anyway, otherwise FALSE
2154 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2157 gboolean retval = TRUE;
2158 GtkTextBuffer *buffer;
2159 GtkTextIter start, end;
2161 if (is_uri_string(uri->uri) == FALSE)
2164 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2166 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2167 gtk_text_buffer_get_iter_at_offset(buffer, &end, uri->end);
2169 visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2171 if (visible_str == NULL)
2174 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2176 gchar *visible_uri_path;
2178 uri_path = get_uri_path(uri->uri);
2179 visible_uri_path = get_uri_path(visible_str);
2180 if (strcmp(uri_path, visible_uri_path) != 0)
2184 if (retval == FALSE) {
2188 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2189 "the apparent URL (%s).\n"
2191 uri->uri, visible_str);
2192 aval = alertpanel_with_type(_("Warning"), msg,
2193 GTK_STOCK_YES, GTK_STOCK_NO,
2194 NULL, NULL, ALERT_WARNING);
2196 if (aval == G_ALERTDEFAULT)
2200 g_free(visible_str);
2205 static void textview_uri_list_remove_all(GSList *uri_list)
2209 for (cur = uri_list; cur != NULL; cur = cur->next) {
2211 g_free(((RemoteURI *)cur->data)->uri);
2212 g_free(((RemoteURI *)cur->data)->filename);
2217 g_slist_free(uri_list);
2220 static void open_uri_cb (TextView *textview, guint action, void *data)
2222 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2227 if (textview_uri_security_check(textview, uri) == TRUE)
2229 prefs_common.uri_cmd);
2230 g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2234 static void save_file_cb (TextView *textview, guint action, void *data)
2236 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->file_popup_menu),
2238 gchar *filename = NULL;
2239 gchar *filepath = NULL;
2240 gchar *filedir = NULL;
2241 gchar *tmp_filename = NULL;
2245 if (uri->filename == NULL)
2248 filename = g_strdup(uri->filename);
2250 if (!g_utf8_validate(filename, -1, NULL)) {
2251 gchar *tmp = conv_filename_to_utf8(filename);
2256 subst_for_filename(filename);
2258 if (prefs_common.attach_save_dir)
2259 filepath = g_strconcat(prefs_common.attach_save_dir,
2260 G_DIR_SEPARATOR_S, filename, NULL);
2262 filepath = g_strdup(filename);
2266 filename = filesel_select_file_save(_("Save as"), filepath);
2272 if (is_file_exist(filename)) {
2276 res = g_strdup_printf(_("Overwrite existing file '%s'?"),
2278 aval = alertpanel(_("Overwrite"), res, GTK_STOCK_OK,
2279 GTK_STOCK_CANCEL, NULL);
2281 if (G_ALERTDEFAULT != aval)
2285 tmp_filename = g_filename_from_uri(uri->uri, NULL, NULL);
2286 copy_file(tmp_filename, filename, FALSE);
2287 g_free(tmp_filename);
2289 filedir = g_path_get_dirname(filename);
2290 if (filedir && strcmp(filedir, ".")) {
2291 if (prefs_common.attach_save_dir)
2292 g_free(prefs_common.attach_save_dir);
2293 prefs_common.attach_save_dir = g_strdup(filedir);
2299 g_object_set_data(G_OBJECT(textview->file_popup_menu), "menu_button",
2303 static void copy_uri_cb (TextView *textview, guint action, void *data)
2305 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2310 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri, -1);
2311 g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2315 static void add_uri_to_addrbook_cb (TextView *textview, guint action, void *data)
2317 gchar *fromname, *fromaddress;
2318 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2324 fromaddress = g_strdup(uri->uri + 7);
2325 /* Hiroyuki: please put this function in utils.c! */
2326 fromname = procheader_get_fromname(fromaddress);
2327 extract_address(fromaddress);
2328 g_message("adding from textview %s <%s>", fromname, fromaddress);
2329 /* Add to address book - Match */
2330 addressbook_add_contact( fromname, fromaddress, NULL );
2332 g_free(fromaddress);
2336 static void mail_to_uri_cb (TextView *textview, guint action, void *data)
2338 PrefsAccount *account = NULL;
2339 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2344 if (textview->messageview && textview->messageview->msginfo &&
2345 textview->messageview->msginfo->folder) {
2346 FolderItem *folder_item;
2348 folder_item = textview->messageview->msginfo->folder;
2349 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2350 account = account_find_from_id(folder_item->prefs->default_account);
2352 compose_new(account, uri->uri + 7, NULL);
2355 static void copy_mail_to_uri_cb (TextView *textview, guint action, void *data)
2357 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2362 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri + 7, -1);
2363 g_object_set_data(G_OBJECT(textview->mail_popup_menu), "menu_button",