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 with Web browser"), NULL, open_uri_cb, 0, NULL},
215 {N_("/Copy this _link"), NULL, copy_uri_cb, 0, NULL},
218 static GtkItemFactoryEntry textview_mail_popup_entries[] =
220 {N_("/Compose _new message"), NULL, mail_to_uri_cb, 0, NULL},
221 {N_("/Add to _address book"), NULL, add_uri_to_addrbook_cb, 0, NULL},
222 {N_("/Copy this add_ress"), 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_reflect_prefs(textview);
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_reflect_prefs(TextView *textview)
421 textview_update_message_colors();
422 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview->text),
423 prefs_common.textview_cursor_visible);
426 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
431 if ((fp = fopen(file, "rb")) == NULL) {
432 FILE_OP_ERROR(file, "fopen");
436 textview_clear(textview);
438 textview_add_parts(textview, mimeinfo);
442 textview_set_position(textview, 0);
445 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
447 g_return_if_fail(mimeinfo != NULL);
448 g_return_if_fail(fp != NULL);
450 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
451 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
452 textview_clear(textview);
453 textview_add_parts(textview, mimeinfo);
457 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
460 textview_clear(textview);
462 if (mimeinfo->type == MIMETYPE_MULTIPART)
463 textview_add_parts(textview, mimeinfo);
465 textview_write_body(textview, mimeinfo);
469 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
472 GtkTextBuffer *buffer;
473 GtkTextIter iter, start_iter;
475 GPtrArray *headers = NULL;
480 g_return_if_fail(mimeinfo != NULL);
481 text = GTK_TEXT_VIEW(textview->text);
482 buffer = gtk_text_view_get_buffer(text);
483 charcount = gtk_text_buffer_get_char_count(buffer);
484 gtk_text_buffer_get_end_iter(buffer, &iter);
486 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
488 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
491 fp = fopen(mimeinfo->data.filename, "rb");
492 fseek(fp, mimeinfo->offset, SEEK_SET);
493 headers = textview_scan_header(textview, fp);
496 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
497 textview_show_header(textview, headers);
498 procheader_header_array_destroy(headers);
504 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
505 content_type = procmime_get_content_type_str(mimeinfo->type,
508 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
510 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
511 name, content_type, mimeinfo->length);
513 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
514 content_type, mimeinfo->length);
516 g_free(content_type);
518 if (mimeinfo->type != MIMETYPE_TEXT) {
519 gtk_text_buffer_insert(buffer, &iter, buf, -1);
520 if (mimeinfo->type == MIMETYPE_IMAGE &&
521 prefs_common.inline_img ) {
523 GError *error = NULL;
529 fp = fopen(mimeinfo->data.filename, "rb");
530 fseek(fp, mimeinfo->offset, SEEK_SET);
532 filename = procmime_get_tmp_file_name(mimeinfo);
533 if (procmime_get_part(filename, mimeinfo) < 0) {
534 g_warning("Can't get the image file.");
539 pixbuf = gdk_pixbuf_new_from_file(filename, &error);
541 g_warning("%s\n", error->message);
545 g_warning("Can't load the image.");
550 if (prefs_common.resize_img) {
551 int new_width, new_height;
553 image_viewer_get_resized_size(gdk_pixbuf_get_width(pixbuf),
554 gdk_pixbuf_get_height(pixbuf),
555 textview->scrolledwin->allocation.width - 100,
556 gdk_pixbuf_get_height(pixbuf),
557 &new_width, &new_height);
558 scaled = gdk_pixbuf_scale_simple
559 (pixbuf, new_width, new_height, GDK_INTERP_BILINEAR);
561 g_object_unref(pixbuf);
565 uri_str = g_filename_to_uri(filename, NULL, NULL);
567 uri = g_new(RemoteURI, 1);
569 uri->start = gtk_text_iter_get_offset(&iter);
571 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf);
573 uri->end = uri->start + 1;
574 uri->filename = procmime_get_part_file_name(mimeinfo);
576 g_slist_append(textview->uri_list, uri);
578 gtk_text_buffer_insert(buffer, &iter, " ", 1);
579 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, uri->start);
580 gtk_text_buffer_apply_tag_by_name(buffer, "link",
583 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf);
584 gtk_text_buffer_insert(buffer, &iter, " ", 1);
587 g_object_unref(pixbuf);
590 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
591 if (prefs_common.display_header && (charcount > 0))
592 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
594 textview_write_body(textview, mimeinfo);
598 static void recursive_add_parts(TextView *textview, GNode *node)
603 mimeinfo = (MimeInfo *) node->data;
605 textview_add_part(textview, mimeinfo);
607 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
608 (mimeinfo->type != MIMETYPE_MESSAGE))
611 if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
612 GNode * prefered_body;
620 prefered_body = NULL;
623 for (iter = g_node_first_child(node) ; iter != NULL ;
624 iter = g_node_next_sibling(iter)) {
629 submime = (MimeInfo *) iter->data;
630 if (submime->type == MIMETYPE_TEXT)
633 if (submime->subtype != NULL) {
634 if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
638 if (score > prefered_score) {
639 prefered_score = score;
640 prefered_body = iter;
644 if (prefered_body != NULL) {
645 recursive_add_parts(textview, prefered_body);
649 for (iter = g_node_first_child(node) ; iter != NULL ;
650 iter = g_node_next_sibling(iter)) {
651 recursive_add_parts(textview, iter);
656 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
658 g_return_if_fail(mimeinfo != NULL);
660 recursive_add_parts(textview, mimeinfo->node);
663 #define TEXT_INSERT(str) \
664 gtk_text_buffer_insert(buffer, &iter, str, -1)
666 void textview_show_error(TextView *textview)
669 GtkTextBuffer *buffer;
672 textview_set_font(textview, NULL);
673 textview_clear(textview);
675 text = GTK_TEXT_VIEW(textview->text);
676 buffer = gtk_text_view_get_buffer(text);
677 gtk_text_buffer_get_start_iter(buffer, &iter);
679 TEXT_INSERT(_("This message can't be displayed.\n"));
683 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
686 GtkTextBuffer *buffer;
689 if (!partinfo) return;
691 textview_set_font(textview, NULL);
692 textview_clear(textview);
694 text = GTK_TEXT_VIEW(textview->text);
695 buffer = gtk_text_view_get_buffer(text);
696 gtk_text_buffer_get_start_iter(buffer, &iter);
698 TEXT_INSERT(_("The following can be performed on this part by "));
699 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
701 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
702 TEXT_INSERT(_(" To display as text select 'Display as text' "));
703 TEXT_INSERT(_("(Shortcut key: 't')\n"));
704 TEXT_INSERT(_(" To open with an external program select 'Open' "));
705 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
706 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
707 TEXT_INSERT(_("mouse button),\n"));
708 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
714 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
719 const gchar *charset;
721 if (textview->messageview->forced_charset)
722 charset = textview->messageview->forced_charset;
724 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
726 textview_set_font(textview, charset);
728 conv = conv_code_converter_new(charset);
730 procmime_force_encoding(textview->messageview->forced_encoding);
732 textview->is_in_signature = FALSE;
734 procmime_decode_content(mimeinfo);
736 if (!g_ascii_strcasecmp(mimeinfo->subtype, "html") &&
737 prefs_common.render_html) {
740 filename = procmime_get_tmp_file_name(mimeinfo);
741 if (procmime_get_part(filename, mimeinfo) == 0) {
742 tmpfp = fopen(filename, "rb");
743 textview_show_html(textview, tmpfp, conv);
748 } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
751 filename = procmime_get_tmp_file_name(mimeinfo);
752 if (procmime_get_part(filename, mimeinfo) == 0) {
753 tmpfp = fopen(filename, "rb");
754 textview_show_ertf(textview, tmpfp, conv);
760 tmpfp = fopen(mimeinfo->data.filename, "rb");
761 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
762 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
763 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
764 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
765 textview_write_line(textview, buf, conv);
769 conv_code_converter_destroy(conv);
770 procmime_force_encoding(0);
773 static void textview_show_html(TextView *textview, FILE *fp,
779 parser = html_parser_new(fp, conv);
780 g_return_if_fail(parser != NULL);
782 while ((str = html_parse(parser)) != NULL) {
783 if (parser->state == HTML_HREF) {
784 /* first time : get and copy the URL */
785 if (parser->href == NULL) {
786 /* ALF - the sylpheed html parser returns an empty string,
787 * if still inside an <a>, but already parsed past HREF */
788 str = strtok(str, " ");
790 parser->href = g_strdup(str);
791 /* the URL may (or not) be followed by the
793 str = strtok(NULL, "");
797 textview_write_link(textview, str, parser->href, NULL);
799 textview_write_line(textview, str, NULL);
801 textview_write_line(textview, "\n", NULL);
802 html_parser_destroy(parser);
805 static void textview_show_ertf(TextView *textview, FILE *fp,
811 parser = ertf_parser_new(fp, conv);
812 g_return_if_fail(parser != NULL);
814 while ((str = ertf_parse(parser)) != NULL) {
815 textview_write_line(textview, str, NULL);
818 ertf_parser_destroy(parser);
821 /* get_uri_part() - retrieves a URI starting from scanpos.
822 Returns TRUE if succesful */
823 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
824 const gchar **bp, const gchar **ep)
828 g_return_val_if_fail(start != NULL, FALSE);
829 g_return_val_if_fail(scanpos != NULL, FALSE);
830 g_return_val_if_fail(bp != NULL, FALSE);
831 g_return_val_if_fail(ep != NULL, FALSE);
835 /* find end point of URI */
836 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
837 if (!isgraph(*(const guchar *)ep_) ||
838 !IS_ASCII(*(const guchar *)ep_) ||
839 strchr("[]{}()<>\"", *ep_))
843 /* no punctuation at end of string */
845 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
846 * should pass some URI type to this function and decide on that whether
847 * to perform punctuation stripping */
849 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
851 for (; ep_ - 1 > scanpos + 1 &&
852 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
863 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
865 return g_strndup(bp, ep - bp);
868 /* valid mail address characters */
869 #define IS_RFC822_CHAR(ch) \
874 !strchr("(),;<>\"", (ch)))
876 /* alphabet and number within 7bit ASCII */
877 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && isalnum(ch))
878 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
880 static GHashTable *create_domain_tab(void)
882 static const gchar *toplvl_domains [] = {
884 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
885 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
886 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
887 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
888 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
889 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
890 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
891 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
892 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
893 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
894 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
895 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
896 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
897 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
898 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
899 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
900 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
901 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
902 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
903 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
904 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
905 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
906 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
907 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
908 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
909 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
912 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
914 g_return_val_if_fail(htab, NULL);
915 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
916 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
920 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
922 const gint MAX_LVL_DOM_NAME_LEN = 6;
923 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
924 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
927 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
930 for (p = buf; p < m && first < last; *p++ = *first++)
934 return g_hash_table_lookup(tab, buf) != NULL;
937 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
938 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
939 const gchar **bp, const gchar **ep)
941 /* more complex than the uri part because we need to scan back and forward starting from
942 * the scan position. */
943 gboolean result = FALSE;
944 const gchar *bp_ = NULL;
945 const gchar *ep_ = NULL;
946 static GHashTable *dom_tab;
947 const gchar *last_dot = NULL;
948 const gchar *prelast_dot = NULL;
949 const gchar *last_tld_char = NULL;
951 /* the informative part of the email address (describing the name
952 * of the email address owner) may contain quoted parts. the
953 * closure stack stores the last encountered quotes. */
954 gchar closure_stack[128];
955 gchar *ptr = closure_stack;
957 g_return_val_if_fail(start != NULL, FALSE);
958 g_return_val_if_fail(scanpos != NULL, FALSE);
959 g_return_val_if_fail(bp != NULL, FALSE);
960 g_return_val_if_fail(ep != NULL, FALSE);
963 dom_tab = create_domain_tab();
964 g_return_val_if_fail(dom_tab, FALSE);
966 /* scan start of address */
967 for (bp_ = scanpos - 1;
968 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
971 /* TODO: should start with an alnum? */
973 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
976 if (bp_ != scanpos) {
977 /* scan end of address */
978 for (ep_ = scanpos + 1;
979 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
981 prelast_dot = last_dot;
983 if (*(last_dot + 1) == '.') {
984 if (prelast_dot == NULL)
986 last_dot = prelast_dot;
991 /* TODO: really should terminate with an alnum? */
992 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
997 if (last_dot == NULL)
1000 last_dot = prelast_dot;
1001 if (last_dot == NULL || (scanpos + 1 >= last_dot))
1005 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
1006 if (*last_tld_char == '?')
1009 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
1016 if (!result) return FALSE;
1018 if (*ep_ && *(bp_ - 1) == '"' && *(ep_) == '"'
1019 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
1020 && IS_RFC822_CHAR(*(ep_ + 3))) {
1021 /* this informative part with an @ in it is
1022 * followed by the email address */
1025 /* go to matching '>' (or next non-rfc822 char, like \n) */
1026 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
1029 /* include the bracket */
1030 if (*ep_ == '>') ep_++;
1032 /* include the leading quote */
1040 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
1041 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
1044 /* see if this is <bracketed>; in this case we also scan for the informative part. */
1045 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
1048 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
1049 #define IN_STACK() (ptr > closure_stack)
1050 /* has underrun check */
1051 #define POP_STACK() if(IN_STACK()) --ptr
1052 /* has overrun check */
1053 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
1054 /* has underrun check */
1055 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
1059 /* scan for the informative part. */
1060 for (bp_ -= 2; bp_ >= start; bp_--) {
1061 /* if closure on the stack keep scanning */
1062 if (PEEK_STACK() == *bp_) {
1066 if (*bp_ == '\'' || *bp_ == '"') {
1071 /* if nothing in the closure stack, do the special conditions
1072 * the following if..else expression simply checks whether
1073 * a token is acceptable. if not acceptable, the clause
1074 * should terminate the loop with a 'break' */
1075 if (!PEEK_STACK()) {
1077 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
1078 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
1079 /* hyphens are allowed, but only in
1081 } else if (!strchr(",;:=?./+<>!&\r\n\t", *bp_)) {
1082 /* but anything not being a punctiation
1085 break; /* anything else is rejected */
1098 /* scan forward (should start with an alnum) */
1099 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
1109 #undef IS_ASCII_ALNUM
1110 #undef IS_RFC822_CHAR
1112 static gchar *make_email_string(const gchar *bp, const gchar *ep)
1114 /* returns a mailto: URI; mailto: is also used to detect the
1115 * uri type later on in the button_pressed signal handler */
1119 tmp = g_strndup(bp, ep - bp);
1120 result = g_strconcat("mailto:", tmp, NULL);
1126 static gchar *make_http_string(const gchar *bp, const gchar *ep)
1128 /* returns an http: URI; */
1132 tmp = g_strndup(bp, ep - bp);
1133 result = g_strconcat("http://", tmp, NULL);
1139 #define ADD_TXT_POS(bp_, ep_, pti_) \
1140 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1141 last = last->next; \
1142 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1143 last->next = NULL; \
1145 g_warning("alloc error scanning URIs\n"); \
1146 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1152 /* textview_make_clickable_parts() - colorizes clickable parts */
1153 static void textview_make_clickable_parts(TextView *textview,
1154 const gchar *fg_tag,
1155 const gchar *uri_tag,
1156 const gchar *linebuf)
1158 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1159 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1161 gchar *mybuf = g_strdup(linebuf);
1163 /* parse table - in order of priority */
1165 const gchar *needle; /* token */
1167 /* token search function */
1168 gchar *(*search) (const gchar *haystack,
1169 const gchar *needle);
1170 /* part parsing function */
1171 gboolean (*parse) (const gchar *start,
1172 const gchar *scanpos,
1175 /* part to URI function */
1176 gchar *(*build_uri) (const gchar *bp,
1180 static struct table parser[] = {
1181 {"http://", strcasestr, get_uri_part, make_uri_string},
1182 {"https://", strcasestr, get_uri_part, make_uri_string},
1183 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1184 {"www.", strcasestr, get_uri_part, make_http_string},
1185 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1186 {"@", strcasestr, get_email_part, make_email_string}
1188 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1191 const gchar *walk, *bp, *ep;
1194 const gchar *bp, *ep; /* text position */
1195 gint pti; /* index in parse table */
1196 struct txtpos *next; /* next */
1197 } head = {NULL, NULL, 0, NULL}, *last = &head;
1199 if (!g_utf8_validate(linebuf, -1, NULL)) {
1200 mybuf = g_malloc(strlen(linebuf)*2 +1);
1201 conv_localetodisp(mybuf, strlen(linebuf)*2 +1, linebuf);
1204 gtk_text_buffer_get_end_iter(buffer, &iter);
1206 /* parse for clickable parts, and build a list of begin and end positions */
1207 for (walk = mybuf, n = 0;;) {
1208 gint last_index = PARSE_ELEMS;
1209 gchar *scanpos = NULL;
1211 /* FIXME: this looks phony. scanning for anything in the parse table */
1212 for (n = 0; n < PARSE_ELEMS; n++) {
1215 tmp = parser[n].search(walk, parser[n].needle);
1217 if (scanpos == NULL || tmp < scanpos) {
1225 /* check if URI can be parsed */
1226 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1227 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1228 ADD_TXT_POS(bp, ep, last_index);
1232 strlen(parser[last_index].needle);
1237 /* colorize this line */
1239 const gchar *normal_text = mybuf;
1242 for (last = head.next; last != NULL;
1243 normal_text = last->ep, last = last->next) {
1245 uri = g_new(RemoteURI, 1);
1246 if (last->bp - normal_text > 0)
1247 gtk_text_buffer_insert_with_tags_by_name
1250 last->bp - normal_text,
1252 uri->uri = parser[last->pti].build_uri(last->bp,
1254 uri->start = gtk_text_iter_get_offset(&iter);
1255 gtk_text_buffer_insert_with_tags_by_name
1256 (buffer, &iter, last->bp, last->ep - last->bp,
1257 uri_tag, fg_tag, NULL);
1258 uri->end = gtk_text_iter_get_offset(&iter);
1259 uri->filename = NULL;
1260 textview->uri_list =
1261 g_slist_append(textview->uri_list, uri);
1265 gtk_text_buffer_insert_with_tags_by_name
1266 (buffer, &iter, normal_text, -1, fg_tag, NULL);
1268 gtk_text_buffer_insert_with_tags_by_name
1269 (buffer, &iter, mybuf, -1, fg_tag, NULL);
1276 static void textview_write_line(TextView *textview, const gchar *str,
1277 CodeConverter *conv)
1280 GtkTextBuffer *buffer;
1282 gchar buf[BUFFSIZE];
1284 gint quotelevel = -1;
1285 gchar quote_tag_str[10];
1287 text = GTK_TEXT_VIEW(textview->text);
1288 buffer = gtk_text_view_get_buffer(text);
1289 gtk_text_buffer_get_end_iter(buffer, &iter);
1292 strncpy2(buf, str, sizeof(buf));
1293 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1294 conv_localetodisp(buf, sizeof(buf), str);
1297 //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1300 /* change color of quotation
1301 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1302 Up to 3 levels of quotations are detected, and each
1303 level is colored using a different color. */
1304 if (prefs_common.enable_color
1305 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1306 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1308 /* set up the correct foreground color */
1309 if (quotelevel > 2) {
1310 /* recycle colors */
1311 if (prefs_common.recycle_quote_colors)
1318 if (quotelevel == -1)
1321 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1322 "quote%d", quotelevel);
1323 fg_color = quote_tag_str;
1326 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1327 fg_color = "signature";
1328 textview->is_in_signature = TRUE;
1331 if (prefs_common.enable_color)
1332 textview_make_clickable_parts(textview, fg_color, "link", buf);
1334 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1337 void textview_write_link(TextView *textview, const gchar *str,
1338 const gchar *uri, CodeConverter *conv)
1340 GdkColor *link_color = NULL;
1342 GtkTextBuffer *buffer;
1344 gchar buf[BUFFSIZE];
1348 if (!str || *str == '\0')
1353 text = GTK_TEXT_VIEW(textview->text);
1354 buffer = gtk_text_view_get_buffer(text);
1355 gtk_text_buffer_get_end_iter(buffer, &iter);
1358 strncpy2(buf, str, sizeof(buf));
1359 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1360 conv_utf8todisp(buf, sizeof(buf), str);
1362 if (g_utf8_validate(buf, -1, NULL) == FALSE)
1367 gtk_text_buffer_get_end_iter(buffer, &iter);
1368 for (bufp = buf; *bufp != '\0'; bufp = g_utf8_next_char(bufp)) {
1371 ch = g_utf8_get_char(bufp);
1372 if (!g_unichar_isspace(ch))
1376 gtk_text_buffer_insert(buffer, &iter, buf, bufp - buf);
1378 if (prefs_common.enable_color) {
1379 link_color = &uri_color;
1381 r_uri = g_new(RemoteURI, 1);
1382 r_uri->uri = g_strdup(uri);
1383 r_uri->start = gtk_text_iter_get_offset(&iter);
1384 gtk_text_buffer_insert_with_tags_by_name
1385 (buffer, &iter, bufp, -1, "link", NULL);
1386 r_uri->end = gtk_text_iter_get_offset(&iter);
1387 r_uri->filename = NULL;
1388 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1391 void textview_clear(TextView *textview)
1393 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1394 GtkTextBuffer *buffer;
1396 buffer = gtk_text_view_get_buffer(text);
1397 gtk_text_buffer_set_text(buffer, "", -1);
1399 TEXTVIEW_STATUSBAR_POP(textview);
1400 textview_uri_list_remove_all(textview->uri_list);
1401 textview->uri_list = NULL;
1403 textview->body_pos = 0;
1406 void textview_destroy(TextView *textview)
1408 textview_uri_list_remove_all(textview->uri_list);
1409 textview->uri_list = NULL;
1414 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1416 textview->show_all_headers = all_headers;
1419 void textview_set_font(TextView *textview, const gchar *codeset)
1421 if (prefs_common.textfont) {
1422 PangoFontDescription *font_desc = NULL;
1424 font_desc = pango_font_description_from_string
1425 (prefs_common.textfont);
1427 gtk_widget_modify_font(textview->text, font_desc);
1428 pango_font_description_free(font_desc);
1431 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1432 prefs_common.line_space / 2);
1433 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1434 prefs_common.line_space / 2);
1437 void textview_set_text(TextView *textview, const gchar *text)
1440 GtkTextBuffer *buffer;
1442 g_return_if_fail(textview != NULL);
1443 g_return_if_fail(text != NULL);
1445 textview_clear(textview);
1447 view = GTK_TEXT_VIEW(textview->text);
1448 buffer = gtk_text_view_get_buffer(view);
1449 gtk_text_buffer_set_text(buffer, text, strlen(text));
1465 H_ORGANIZATION = 11,
1468 void textview_set_position(TextView *textview, gint pos)
1470 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1471 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1474 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1475 gtk_text_buffer_place_cursor(buffer, &iter);
1476 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1479 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1481 gchar buf[BUFFSIZE];
1482 GPtrArray *headers, *sorted_headers;
1483 GSList *disphdr_list;
1487 g_return_val_if_fail(fp != NULL, NULL);
1489 if (textview->show_all_headers)
1490 return procheader_get_header_array_asis(fp);
1492 if (!prefs_common.display_header) {
1493 while (fgets(buf, sizeof(buf), fp) != NULL)
1494 if (buf[0] == '\r' || buf[0] == '\n') break;
1498 headers = procheader_get_header_array_asis(fp);
1500 sorted_headers = g_ptr_array_new();
1502 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1503 disphdr_list = disphdr_list->next) {
1504 DisplayHeaderProp *dp =
1505 (DisplayHeaderProp *)disphdr_list->data;
1507 for (i = 0; i < headers->len; i++) {
1508 header = g_ptr_array_index(headers, i);
1510 if (procheader_headername_equal(header->name,
1513 procheader_header_free(header);
1515 g_ptr_array_add(sorted_headers, header);
1517 g_ptr_array_remove_index(headers, i);
1523 if (prefs_common.show_other_header) {
1524 for (i = 0; i < headers->len; i++) {
1525 header = g_ptr_array_index(headers, i);
1526 g_ptr_array_add(sorted_headers, header);
1528 g_ptr_array_free(headers, TRUE);
1530 procheader_header_array_destroy(headers);
1533 return sorted_headers;
1536 static void textview_show_header(TextView *textview, GPtrArray *headers)
1538 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1539 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1544 g_return_if_fail(headers != NULL);
1546 for (i = 0; i < headers->len; i++) {
1547 header = g_ptr_array_index(headers, i);
1548 g_return_if_fail(header->name != NULL);
1550 gtk_text_buffer_get_end_iter (buffer, &iter);
1551 gtk_text_buffer_insert_with_tags_by_name
1552 (buffer, &iter, header->name, -1,
1553 "header_title", "header", NULL);
1554 if (header->name[strlen(header->name) - 1] != ' ')
1555 gtk_text_buffer_insert_with_tags_by_name
1556 (buffer, &iter, " ", 1,
1557 "header_title", "header", NULL);
1559 if (procheader_headername_equal(header->name, "Subject") ||
1560 procheader_headername_equal(header->name, "From") ||
1561 procheader_headername_equal(header->name, "To") ||
1562 procheader_headername_equal(header->name, "Cc"))
1563 unfold_line(header->body);
1565 if (prefs_common.enable_color &&
1566 (procheader_headername_equal(header->name, "X-Mailer") ||
1567 procheader_headername_equal(header->name,
1569 strstr(header->body, "Sylpheed") != NULL) {
1570 gtk_text_buffer_get_end_iter (buffer, &iter);
1571 gtk_text_buffer_insert_with_tags_by_name
1572 (buffer, &iter, header->body, -1,
1573 "header", "emphasis", NULL);
1574 } else if (prefs_common.enable_color) {
1575 textview_make_clickable_parts(textview, "header", "link",
1578 textview_make_clickable_parts(textview, "header", NULL,
1581 gtk_text_buffer_get_end_iter (buffer, &iter);
1582 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1587 gboolean textview_search_string(TextView *textview, const gchar *str,
1590 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1591 GtkTextBuffer *buffer;
1592 GtkTextIter iter, match_pos;
1596 g_return_val_if_fail(str != NULL, FALSE);
1598 buffer = gtk_text_view_get_buffer(text);
1600 len = g_utf8_strlen(str, -1);
1601 g_return_val_if_fail(len >= 0, FALSE);
1603 mark = gtk_text_buffer_get_insert(buffer);
1604 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1606 if (gtkut_text_buffer_find(buffer, &iter, str, case_sens,
1608 GtkTextIter end = match_pos;
1610 gtk_text_iter_forward_chars(&end, len);
1611 /* place "insert" at the last character */
1612 gtk_text_buffer_select_range(buffer, &end, &match_pos);
1613 gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1620 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1623 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1624 GtkTextBuffer *buffer;
1625 GtkTextIter iter, match_pos;
1629 g_return_val_if_fail(str != NULL, FALSE);
1631 buffer = gtk_text_view_get_buffer(text);
1633 len = g_utf8_strlen(str, -1);
1634 g_return_val_if_fail(len >= 0, FALSE);
1636 mark = gtk_text_buffer_get_insert(buffer);
1637 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1639 if (gtkut_text_buffer_find_backward(buffer, &iter, str, case_sens,
1641 GtkTextIter end = match_pos;
1643 gtk_text_iter_forward_chars(&end, len);
1644 gtk_text_buffer_select_range(buffer, &match_pos, &end);
1645 gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1652 void textview_scroll_one_line(TextView *textview, gboolean up)
1654 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1655 GtkAdjustment *vadj = text->vadjustment;
1658 if (prefs_common.enable_smooth_scroll) {
1659 textview_smooth_scroll_one_line(textview, up);
1664 upper = vadj->upper - vadj->page_size;
1665 if (vadj->value < upper) {
1666 vadj->value += vadj->step_increment;
1667 vadj->value = MIN(vadj->value, upper);
1668 g_signal_emit_by_name(G_OBJECT(vadj),
1669 "value_changed", 0);
1672 if (vadj->value > 0.0) {
1673 vadj->value -= vadj->step_increment;
1674 vadj->value = MAX(vadj->value, 0.0);
1675 g_signal_emit_by_name(G_OBJECT(vadj),
1676 "value_changed", 0);
1681 gboolean textview_scroll_page(TextView *textview, gboolean up)
1683 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1684 GtkAdjustment *vadj = text->vadjustment;
1688 if (prefs_common.enable_smooth_scroll)
1689 return textview_smooth_scroll_page(textview, up);
1691 if (prefs_common.scroll_halfpage)
1692 page_incr = vadj->page_increment / 2;
1694 page_incr = vadj->page_increment;
1697 upper = vadj->upper - vadj->page_size;
1698 if (vadj->value < upper) {
1699 vadj->value += page_incr;
1700 vadj->value = MIN(vadj->value, upper);
1701 g_signal_emit_by_name(G_OBJECT(vadj),
1702 "value_changed", 0);
1706 if (vadj->value > 0.0) {
1707 vadj->value -= page_incr;
1708 vadj->value = MAX(vadj->value, 0.0);
1709 g_signal_emit_by_name(G_OBJECT(vadj),
1710 "value_changed", 0);
1718 static void textview_smooth_scroll_do(TextView *textview,
1719 gfloat old_value, gfloat last_value,
1722 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1723 GtkAdjustment *vadj = text->vadjustment;
1728 if (old_value < last_value) {
1729 change_value = last_value - old_value;
1732 change_value = old_value - last_value;
1736 for (i = step; i <= change_value; i += step) {
1737 vadj->value = old_value + (up ? -i : i);
1738 g_signal_emit_by_name(G_OBJECT(vadj),
1739 "value_changed", 0);
1742 vadj->value = last_value;
1743 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1745 gtk_widget_queue_draw(GTK_WIDGET(text));
1748 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1750 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1751 GtkAdjustment *vadj = text->vadjustment;
1757 upper = vadj->upper - vadj->page_size;
1758 if (vadj->value < upper) {
1759 old_value = vadj->value;
1760 last_value = vadj->value + vadj->step_increment;
1761 last_value = MIN(last_value, upper);
1763 textview_smooth_scroll_do(textview, old_value,
1765 prefs_common.scroll_step);
1768 if (vadj->value > 0.0) {
1769 old_value = vadj->value;
1770 last_value = vadj->value - vadj->step_increment;
1771 last_value = MAX(last_value, 0.0);
1773 textview_smooth_scroll_do(textview, old_value,
1775 prefs_common.scroll_step);
1780 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1782 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1783 GtkAdjustment *vadj = text->vadjustment;
1789 if (prefs_common.scroll_halfpage)
1790 page_incr = vadj->page_increment / 2;
1792 page_incr = vadj->page_increment;
1795 upper = vadj->upper - vadj->page_size;
1796 if (vadj->value < upper) {
1797 old_value = vadj->value;
1798 last_value = vadj->value + page_incr;
1799 last_value = MIN(last_value, upper);
1801 textview_smooth_scroll_do(textview, old_value,
1803 prefs_common.scroll_step);
1807 if (vadj->value > 0.0) {
1808 old_value = vadj->value;
1809 last_value = vadj->value - page_incr;
1810 last_value = MAX(last_value, 0.0);
1812 textview_smooth_scroll_do(textview, old_value,
1814 prefs_common.scroll_step);
1822 #define KEY_PRESS_EVENT_STOP() \
1823 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1826 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1829 SummaryView *summaryview = NULL;
1830 MessageView *messageview = textview->messageview;
1832 if (!event) return FALSE;
1833 if (messageview->mainwin)
1834 summaryview = messageview->mainwin->summaryview;
1836 switch (event->keyval) {
1851 summary_pass_key_press_event(summaryview, event);
1853 textview_scroll_page
1856 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1859 textview_scroll_page(textview, TRUE);
1862 textview_scroll_one_line
1863 (textview, (event->state &
1864 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1868 summary_pass_key_press_event(summaryview, event);
1873 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1874 KEY_PRESS_EVENT_STOP();
1875 mimeview_pass_key_press_event(messageview->mimeview,
1879 /* possible fall through */
1882 event->window != messageview->mainwin->window->window) {
1883 GdkEventKey tmpev = *event;
1885 tmpev.window = messageview->mainwin->window->window;
1886 KEY_PRESS_EVENT_STOP();
1887 gtk_widget_event(messageview->mainwin->window,
1888 (GdkEvent *)&tmpev);
1896 static gboolean textview_motion_notify(GtkWidget *widget,
1897 GdkEventMotion *event,
1900 textview_uri_update(textview, event->x, event->y);
1901 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1906 static gboolean textview_leave_notify(GtkWidget *widget,
1907 GdkEventCrossing *event,
1910 textview_uri_update(textview, -1, -1);
1915 static gboolean textview_visibility_notify(GtkWidget *widget,
1916 GdkEventVisibility *event,
1922 window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1923 GTK_TEXT_WINDOW_TEXT);
1925 /* check if occurred for the text window part */
1926 if (window != event->window)
1929 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1930 textview_uri_update(textview, wx, wy);
1935 static void textview_uri_update(TextView *textview, gint x, gint y)
1937 GtkTextBuffer *buffer;
1938 GtkTextIter start_iter, end_iter;
1939 RemoteURI *uri = NULL;
1941 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1943 if (x != -1 && y != -1) {
1949 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1950 GTK_TEXT_WINDOW_WIDGET,
1952 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1955 tags = gtk_text_iter_get_tags(&iter);
1956 for (cur = tags; cur != NULL; cur = cur->next) {
1957 GtkTextTag *tag = cur->data;
1960 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1961 if (!strcmp(name, "link")
1962 && textview_get_uri_range(textview, &iter, tag,
1963 &start_iter, &end_iter))
1964 uri = textview_get_uri_from_range(textview,
1976 if (uri != textview->uri_hover) {
1979 if (textview->uri_hover)
1980 gtk_text_buffer_remove_tag_by_name(buffer,
1982 &textview->uri_hover_start_iter,
1983 &textview->uri_hover_end_iter);
1985 textview->uri_hover = uri;
1987 textview->uri_hover_start_iter = start_iter;
1988 textview->uri_hover_end_iter = end_iter;
1991 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1992 GTK_TEXT_WINDOW_TEXT);
1993 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1995 TEXTVIEW_STATUSBAR_POP(textview);
2000 gtk_text_buffer_apply_tag_by_name(buffer,
2005 trimmed_uri = trim_string(uri->uri, 60);
2006 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
2007 g_free(trimmed_uri);
2012 static gboolean textview_get_uri_range(TextView *textview,
2015 GtkTextIter *start_iter,
2016 GtkTextIter *end_iter)
2018 GtkTextIter _start_iter, _end_iter;
2021 if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
2022 debug_print("Can't find end");
2026 _start_iter = _end_iter;
2027 if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
2028 debug_print("Can't find start.");
2032 *start_iter = _start_iter;
2033 *end_iter = _end_iter;
2038 static RemoteURI *textview_get_uri_from_range(TextView *textview,
2041 GtkTextIter *start_iter,
2042 GtkTextIter *end_iter)
2044 gint start_pos, end_pos, cur_pos;
2045 RemoteURI *uri = NULL;
2048 start_pos = gtk_text_iter_get_offset(start_iter);
2049 end_pos = gtk_text_iter_get_offset(end_iter);
2050 cur_pos = gtk_text_iter_get_offset(iter);
2052 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2053 RemoteURI *uri_ = (RemoteURI *)cur->data;
2054 if (start_pos == uri_->start &&
2055 end_pos == uri_->end) {
2058 } else if (start_pos == uri_->start ||
2059 end_pos == uri_->end) {
2060 /* in case of contiguous links, textview_get_uri_range
2061 * returns a broader range (start of 1st link to end
2063 * In that case, correct link is the one covering
2066 if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
2076 static RemoteURI *textview_get_uri(TextView *textview,
2080 GtkTextIter start_iter, end_iter;
2081 RemoteURI *uri = NULL;
2083 if (textview_get_uri_range(textview, iter, tag, &start_iter,
2085 uri = textview_get_uri_from_range(textview, iter, tag,
2086 &start_iter, &end_iter);
2091 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2092 GdkEvent *event, GtkTextIter *iter,
2095 GdkEventButton *bevent;
2096 RemoteURI *uri = NULL;
2101 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2102 && event->type != GDK_MOTION_NOTIFY)
2105 uri = textview_get_uri(textview, iter, tag);
2109 bevent = (GdkEventButton *) event;
2111 /* doubleclick: open compose / add address / browser */
2112 if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2113 bevent->button == 2 || bevent->button == 3) {
2114 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2115 if (bevent->button == 3) {
2117 G_OBJECT(textview->mail_popup_menu),
2118 "menu_button", uri);
2119 gtk_menu_popup(GTK_MENU(textview->mail_popup_menu),
2120 NULL, NULL, NULL, NULL,
2121 bevent->button, bevent->time);
2123 PrefsAccount *account = NULL;
2125 if (textview->messageview && textview->messageview->msginfo &&
2126 textview->messageview->msginfo->folder) {
2127 FolderItem *folder_item;
2129 folder_item = textview->messageview->msginfo->folder;
2130 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2131 account = account_find_from_id(folder_item->prefs->default_account);
2133 compose_new(account, uri->uri + 7, NULL);
2136 } else if (g_ascii_strncasecmp(uri->uri, "file:", 5)) {
2137 if (bevent->button == 1 &&
2138 textview_uri_security_check(textview, uri) == TRUE)
2140 prefs_common.uri_cmd);
2141 else if (bevent->button == 3) {
2143 G_OBJECT(textview->link_popup_menu),
2144 "menu_button", uri);
2145 gtk_menu_popup(GTK_MENU(textview->link_popup_menu),
2146 NULL, NULL, NULL, NULL,
2147 bevent->button, bevent->time);
2151 if (bevent->button == 3) {
2153 G_OBJECT(textview->file_popup_menu),
2154 "menu_button", uri);
2155 gtk_menu_popup(GTK_MENU(textview->file_popup_menu),
2156 NULL, NULL, NULL, NULL,
2157 bevent->button, bevent->time);
2167 *\brief Check to see if a web URL has been disguised as a different
2168 * URL (possible with HTML email).
2170 *\param uri The uri to check
2172 *\param textview The TextView the URL is contained in
2174 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2175 * it anyway, otherwise FALSE
2177 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2180 gboolean retval = TRUE;
2181 GtkTextBuffer *buffer;
2182 GtkTextIter start, end;
2184 if (is_uri_string(uri->uri) == FALSE)
2187 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2189 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2190 gtk_text_buffer_get_iter_at_offset(buffer, &end, uri->end);
2192 visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2194 if (visible_str == NULL)
2197 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2199 gchar *visible_uri_path;
2201 uri_path = get_uri_path(uri->uri);
2202 visible_uri_path = get_uri_path(visible_str);
2203 if (strcmp(uri_path, visible_uri_path) != 0)
2207 if (retval == FALSE) {
2211 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2212 "the apparent URL (%s).\n"
2215 uri->uri, visible_str);
2216 aval = alertpanel_full(_("Fake URL warning"), msg,
2217 GTK_STOCK_YES, GTK_STOCK_NO, NULL, FALSE,
2218 NULL, ALERT_WARNING, G_ALERTALTERNATE);
2220 if (aval == G_ALERTDEFAULT)
2224 g_free(visible_str);
2229 static void textview_uri_list_remove_all(GSList *uri_list)
2233 for (cur = uri_list; cur != NULL; cur = cur->next) {
2235 g_free(((RemoteURI *)cur->data)->uri);
2236 g_free(((RemoteURI *)cur->data)->filename);
2241 g_slist_free(uri_list);
2244 static void open_uri_cb (TextView *textview, guint action, void *data)
2246 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2251 if (textview_uri_security_check(textview, uri) == TRUE)
2253 prefs_common.uri_cmd);
2254 g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2258 static void save_file_cb (TextView *textview, guint action, void *data)
2260 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->file_popup_menu),
2262 gchar *filename = NULL;
2263 gchar *filepath = NULL;
2264 gchar *filedir = NULL;
2265 gchar *tmp_filename = NULL;
2269 if (uri->filename == NULL)
2272 filename = g_strdup(uri->filename);
2274 if (!g_utf8_validate(filename, -1, NULL)) {
2275 gchar *tmp = conv_filename_to_utf8(filename);
2280 subst_for_filename(filename);
2282 if (prefs_common.attach_save_dir)
2283 filepath = g_strconcat(prefs_common.attach_save_dir,
2284 G_DIR_SEPARATOR_S, filename, NULL);
2286 filepath = g_strdup(filename);
2290 filename = filesel_select_file_save(_("Save as"), filepath);
2296 if (is_file_exist(filename)) {
2300 res = g_strdup_printf(_("Overwrite existing file '%s'?"),
2302 aval = alertpanel(_("Overwrite"), res, GTK_STOCK_OK,
2303 GTK_STOCK_CANCEL, NULL);
2305 if (G_ALERTDEFAULT != aval)
2309 tmp_filename = g_filename_from_uri(uri->uri, NULL, NULL);
2310 copy_file(tmp_filename, filename, FALSE);
2311 g_free(tmp_filename);
2313 filedir = g_path_get_dirname(filename);
2314 if (filedir && strcmp(filedir, ".")) {
2315 if (prefs_common.attach_save_dir)
2316 g_free(prefs_common.attach_save_dir);
2317 prefs_common.attach_save_dir = g_strdup(filedir);
2323 g_object_set_data(G_OBJECT(textview->file_popup_menu), "menu_button",
2327 static void copy_uri_cb (TextView *textview, guint action, void *data)
2329 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2334 gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), uri->uri, -1);
2335 gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), uri->uri, -1);
2336 g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2340 static void add_uri_to_addrbook_cb (TextView *textview, guint action, void *data)
2342 gchar *fromname, *fromaddress;
2343 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2349 fromaddress = g_strdup(uri->uri + 7);
2350 /* Hiroyuki: please put this function in utils.c! */
2351 fromname = procheader_get_fromname(fromaddress);
2352 extract_address(fromaddress);
2353 g_message("adding from textview %s <%s>", fromname, fromaddress);
2354 /* Add to address book - Match */
2355 addressbook_add_contact( fromname, fromaddress, NULL );
2357 g_free(fromaddress);
2361 static void mail_to_uri_cb (TextView *textview, guint action, void *data)
2363 PrefsAccount *account = NULL;
2364 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2369 if (textview->messageview && textview->messageview->msginfo &&
2370 textview->messageview->msginfo->folder) {
2371 FolderItem *folder_item;
2373 folder_item = textview->messageview->msginfo->folder;
2374 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2375 account = account_find_from_id(folder_item->prefs->default_account);
2377 compose_new(account, uri->uri + 7, NULL);
2380 static void copy_mail_to_uri_cb (TextView *textview, guint action, void *data)
2382 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2387 gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), uri->uri +7, -1);
2388 gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), uri->uri +7, -1);
2389 g_object_set_data(G_OBJECT(textview->mail_popup_menu), "menu_button",