talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

textview.c (68906B)


      1 /*
      2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
      3  * Copyright (C) 1999-2025 the Claws Mail team and Hiroyuki Yamamoto
      4  *
      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 3 of the License, or
      8  * (at your option) any later version.
      9  *
     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.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
     17  */
     18 
     19 #include "defs.h"
     20 
     21 #include <glib.h>
     22 #include <glib/gi18n.h>
     23 #include <gdk/gdk.h>
     24 #include <gdk/gdkkeysyms.h>
     25 #include <gtk/gtk.h>
     26 #include <stdio.h>
     27 #include <ctype.h>
     28 #include <string.h>
     29 #include <stdlib.h>
     30 #include <errno.h>
     31 #include <sys/wait.h>
     32 
     33 #include "main.h"
     34 #include "summaryview.h"
     35 #include "procheader.h"
     36 #include "prefs_common.h"
     37 #include "codeconv.h"
     38 #include "utils.h"
     39 #include "gtkutils.h"
     40 #include "procmime.h"
     41 #include "html.h"
     42 #include "compose.h"
     43 #include "addressbook.h"
     44 #include "addrindex.h"
     45 #include "displayheader.h"
     46 #include "account.h"
     47 #include "mimeview.h"
     48 #include "alertpanel.h"
     49 #include "menu.h"
     50 #include "filesel.h"
     51 #include "inputdialog.h"
     52 #include "tags.h"
     53 #include "manage_window.h"
     54 #include "folder_item_prefs.h"
     55 #include "hooks.h"
     56 #include "file-utils.h"
     57 #include "fence.h"
     58 
     59 static GdkRGBA quote_colors[3] = {
     60 	{0, 0, 0, 1},
     61 	{0, 0, 0, 1},
     62 	{0, 0, 0, 1}
     63 };
     64 
     65 static GdkRGBA quote_bgcolors[3] = {
     66 	{0, 0, 0, 1},
     67 	{0, 0, 0, 1},
     68 	{0, 0, 0, 1}
     69 };
     70 static GdkRGBA signature_color = {
     71 	0.5, 0.5, 0.5, 1
     72 };
     73 
     74 static GdkRGBA uri_color = {
     75 	0, 0, 0, 1
     76 };
     77 
     78 static GdkRGBA emphasis_color = {
     79 	0, 0, 0, 1
     80 };
     81 
     82 static GdkRGBA diff_added_color = {
     83 	0, 0, 0, 1
     84 };
     85 
     86 static GdkRGBA diff_deleted_color = {
     87 	0, 0, 0, 1
     88 };
     89 
     90 static GdkRGBA diff_hunk_color = {
     91 	0, 0, 0, 1
     92 };
     93 
     94 static GdkRGBA tags_bgcolor = {
     95 	0, 0, 0, 1
     96 };
     97 
     98 static GdkRGBA tags_color = {
     99 	0, 0, 0, 1
    100 };
    101 
    102 static GdkCursor *hand_cursor = NULL;
    103 static GdkCursor *text_cursor = NULL;
    104 static GdkCursor *watch_cursor= NULL;
    105 
    106 #define WIDTH 48
    107 #define HEIGHT 48
    108 
    109 #define TEXTVIEW_STATUSBAR_PUSH(textview, str)				    \
    110 {	if (textview->messageview->statusbar)				    \
    111 	gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
    112 			   textview->messageview->statusbar_cid, str);	    \
    113 }
    114 
    115 #define TEXTVIEW_STATUSBAR_POP(textview)				   \
    116 {	if (textview->messageview->statusbar)				   \
    117 	gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
    118 			  textview->messageview->statusbar_cid);	   \
    119 }
    120 
    121 static void textview_add_part		(TextView	*textview,
    122 					 MimeInfo	*mimeinfo);
    123 static void textview_add_parts		(TextView	*textview,
    124 					 MimeInfo	*mimeinfo);
    125 static void textview_write_body		(TextView	*textview,
    126 					 MimeInfo	*mimeinfo);
    127 static void textview_show_html		(TextView	*textview,
    128 					 FILE		*fp,
    129 					 CodeConverter	*conv);
    130 
    131 static void textview_write_line		(TextView	*textview,
    132 					 const gchar	*str,
    133 					 CodeConverter	*conv,
    134 					 gboolean	 do_quote_folding);
    135 static void textview_write_link		(TextView	*textview,
    136 					 const gchar	*str,
    137 					 const gchar	*uri,
    138 					 CodeConverter	*conv);
    139 
    140 static GPtrArray *textview_scan_header	(TextView	*textview,
    141 					 FILE		*fp);
    142 static void textview_show_header	(TextView	*textview,
    143 					 GPtrArray	*headers);
    144 
    145 static gboolean textview_motion_notify		(GtkWidget	*widget,
    146 						 GdkEventMotion	*motion,
    147 						 TextView	*textview);
    148 static gboolean textview_leave_notify		(GtkWidget	  *widget,
    149 						 GdkEventCrossing *event,
    150 						 TextView	  *textview);
    151 static gboolean textview_visibility_notify	(GtkWidget	*widget,
    152 						 GdkEventVisibility *event,
    153 						 TextView	*textview);
    154 static void textview_uri_update			(TextView	*textview,
    155 						 gint		x,
    156 						 gint		y);
    157 static gboolean textview_get_uri_range		(TextView	*textview,
    158 						 GtkTextIter	*iter,
    159 						 GtkTextTag	*tag,
    160 						 GtkTextIter	*start_iter,
    161 						 GtkTextIter	*end_iter);
    162 static ClickableText *textview_get_uri_from_range	(TextView	*textview,
    163 						 GtkTextIter	*iter,
    164 						 GtkTextTag	*tag,
    165 						 GtkTextIter	*start_iter,
    166 						 GtkTextIter	*end_iter);
    167 static ClickableText *textview_get_uri		(TextView	*textview,
    168 						 GtkTextIter	*iter,
    169 						 GtkTextTag	*tag);
    170 static gboolean textview_uri_button_pressed	(GtkTextTag 	*tag,
    171 						 GObject 	*obj,
    172 						 GdkEvent 	*event,
    173 						 GtkTextIter	*iter,
    174 						 TextView 	*textview);
    175 
    176 static void textview_uri_list_remove_all	(GSList		*uri_list);
    177 
    178 static void textview_toggle_quote		(TextView 	*textview,
    179 						 GSList		*start_list,
    180 						 ClickableText 	*uri,
    181 						 gboolean	 expand_only);
    182 
    183 static void open_uri_cb				(GtkAction	*action,
    184 						 TextView	*textview);
    185 static void copy_uri_cb				(GtkAction	*action,
    186 						 TextView	*textview);
    187 static void add_uri_to_addrbook_cb 		(GtkAction	*action,
    188 						 TextView	*textview);
    189 static void reply_to_uri_cb 			(GtkAction	*action,
    190 						 TextView	*textview);
    191 static void mail_to_uri_cb 			(GtkAction	*action,
    192 						 TextView	*textview);
    193 static void copy_mail_to_uri_cb			(GtkAction	*action,
    194 						 TextView	*textview);
    195 
    196 static GtkActionEntry textview_link_popup_entries[] =
    197 {
    198 	{"TextviewPopupLink",			NULL, "TextviewPopupLink", NULL, NULL, NULL },
    199 	{"TextviewPopupLink/Open",		NULL, N_("_Open in web browser"), NULL, NULL, G_CALLBACK(open_uri_cb) },
    200 	{"TextviewPopupLink/Copy",		NULL, N_("Copy this _link"), NULL, NULL, G_CALLBACK(copy_uri_cb) },
    201 };
    202 
    203 static GtkActionEntry textview_mail_popup_entries[] =
    204 {
    205 	{"TextviewPopupMail",			NULL, "TextviewPopupMail", NULL, NULL, NULL },
    206 	{"TextviewPopupMail/Compose",		NULL, N_("Write _new message"), NULL, NULL, G_CALLBACK(mail_to_uri_cb) },
    207 	{"TextviewPopupMail/ReplyTo",		NULL, N_("_Reply to this address"), NULL, NULL, G_CALLBACK(reply_to_uri_cb) },
    208 	{"TextviewPopupMail/AddAB",		NULL, N_("Add to _Address book"), NULL, NULL, G_CALLBACK(add_uri_to_addrbook_cb) },
    209 	{"TextviewPopupMail/Copy",		NULL, N_("Copy this add_ress"), NULL, NULL, G_CALLBACK(copy_mail_to_uri_cb) },
    210 };
    211 
    212 static void textview_size_allocate_cb	(GtkWidget	*widget,
    213 					 GtkAllocation	*allocation,
    214 					 gpointer	 data)
    215 {
    216 }
    217 
    218 TextView *textview_create(void)
    219 {
    220 	TextView *textview;
    221 	GtkWidget *vbox;
    222 	GtkWidget *scrolledwin;
    223 	GtkWidget *text;
    224 	GtkTextBuffer *buffer;
    225 	GtkClipboard *clipboard;
    226 
    227 	debug_print("Creating text view...\n");
    228 	textview = g_new0(TextView, 1);
    229 
    230 	scrolledwin = gtk_scrolled_window_new(NULL, NULL);
    231 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
    232 				       GTK_POLICY_AUTOMATIC,
    233 				       GTK_POLICY_AUTOMATIC);
    234 	gtk_widget_set_vexpand(GTK_WIDGET(scrolledwin), TRUE);
    235 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
    236 					    GTK_SHADOW_IN);
    237 
    238 	/* create GtkSText widgets for single-byte and multi-byte character */
    239 	text = gtk_text_view_new();
    240 	gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
    241 	gtk_widget_show(text);
    242 	gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
    243 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
    244 	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
    245 	gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
    246 	gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
    247 
    248 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
    249 	clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
    250 	gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
    251 
    252 	g_object_ref(scrolledwin);
    253 
    254 	gtk_container_add(GTK_CONTAINER(scrolledwin), text);
    255 
    256 	g_signal_connect(G_OBJECT(text), "motion-notify-event",
    257 			 G_CALLBACK(textview_motion_notify), textview);
    258 	g_signal_connect(G_OBJECT(text), "leave-notify-event",
    259 			 G_CALLBACK(textview_leave_notify), textview);
    260 	g_signal_connect(G_OBJECT(text), "visibility-notify-event",
    261 			 G_CALLBACK(textview_visibility_notify), textview);
    262 	g_signal_connect(G_OBJECT(text), "size_allocate",
    263 			 G_CALLBACK(textview_size_allocate_cb),
    264 			 textview);
    265 
    266 	gtk_widget_show(scrolledwin);
    267 
    268 	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    269 	gtk_widget_set_name(GTK_WIDGET(vbox), "textview");
    270 	gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
    271 
    272 	gtk_widget_show(vbox);
    273 
    274 	textview->ui_manager = gtk_ui_manager_new();
    275 	textview->link_action_group = cm_menu_create_action_group_full(textview->ui_manager,
    276 			"TextviewPopupLink",
    277 			textview_link_popup_entries,
    278 			G_N_ELEMENTS(textview_link_popup_entries), (gpointer)textview);
    279 	textview->mail_action_group = cm_menu_create_action_group_full(textview->ui_manager,
    280 			"TextviewPopupMail",
    281 			textview_mail_popup_entries,
    282 			G_N_ELEMENTS(textview_mail_popup_entries), (gpointer)textview);
    283 
    284 	MENUITEM_ADDUI_MANAGER(textview->ui_manager, "/", "Menus", "Menus", GTK_UI_MANAGER_MENUBAR)
    285 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
    286 			"/Menus", "TextviewPopupLink", "TextviewPopupLink", GTK_UI_MANAGER_MENU)
    287 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
    288 			"/Menus", "TextviewPopupMail", "TextviewPopupMail", GTK_UI_MANAGER_MENU)
    289 
    290 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
    291 			"/Menus/TextviewPopupLink", "Open", "TextviewPopupLink/Open", GTK_UI_MANAGER_MENUITEM)
    292 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
    293 			"/Menus/TextviewPopupLink", "Copy", "TextviewPopupLink/Copy", GTK_UI_MANAGER_MENUITEM)
    294 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
    295 			"/Menus/TextviewPopupMail", "Compose", "TextviewPopupMail/Compose", GTK_UI_MANAGER_MENUITEM)
    296 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
    297 			"/Menus/TextviewPopupMail", "ReplyTo", "TextviewPopupMail/ReplyTo", GTK_UI_MANAGER_MENUITEM)
    298 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
    299 			"/Menus/TextviewPopupMail", "AddAB", "TextviewPopupMail/AddAB", GTK_UI_MANAGER_MENUITEM)
    300 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
    301 			"/Menus/TextviewPopupMail", "Copy", "TextviewPopupMail/Copy", GTK_UI_MANAGER_MENUITEM)
    302 
    303 	textview->link_popup_menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
    304 				gtk_ui_manager_get_widget(textview->ui_manager, "/Menus/TextviewPopupLink")) );
    305 	textview->mail_popup_menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
    306 				gtk_ui_manager_get_widget(textview->ui_manager, "/Menus/TextviewPopupMail")) );
    307 
    308 	textview->vbox               = vbox;
    309 	textview->scrolledwin        = scrolledwin;
    310 	textview->text               = text;
    311 	textview->uri_list           = NULL;
    312 	textview->body_pos           = 0;
    313 	textview->last_buttonpress   = GDK_NOTHING;
    314 	textview->image		     = NULL;
    315 	return textview;
    316 }
    317 
    318 static void textview_create_tags(GtkTextView *text, TextView *textview)
    319 {
    320 	GtkTextBuffer *buffer;
    321 	GtkTextTag *tag, *qtag;
    322 
    323 	PangoFontDescription *font_desc = default_font();
    324 	PangoFontDescription *bold_font_desc = default_font();
    325 	pango_font_description_set_weight(bold_font_desc, PANGO_WEIGHT_BOLD);
    326 
    327 	buffer = gtk_text_view_get_buffer(text);
    328 
    329 	gtk_text_buffer_create_tag(buffer, "header",
    330 				   "pixels-above-lines", 0,
    331 				   "pixels-above-lines-set", TRUE,
    332 				   "pixels-below-lines", 0,
    333 				   "pixels-below-lines-set", TRUE,
    334 				   "font-desc", font_desc,
    335 				   "left-margin", 3,
    336 				   "left-margin-set", TRUE,
    337 				   NULL);
    338 	gtk_text_buffer_create_tag(buffer, "header_title",
    339 				   "font-desc", bold_font_desc,
    340 				   NULL);
    341 	tag = gtk_text_buffer_create_tag(buffer, "hlink",
    342 				   "pixels-above-lines", 0,
    343 				   "pixels-above-lines-set", TRUE,
    344 				   "pixels-below-lines", 0,
    345 				   "pixels-below-lines-set", TRUE,
    346 				   "font-desc", font_desc,
    347 				   "left-margin", 3,
    348 				   "left-margin-set", TRUE,
    349 				   "foreground-rgba", &uri_color,
    350 				   NULL);
    351 	g_signal_connect(G_OBJECT(tag), "event",
    352 				   G_CALLBACK(textview_uri_button_pressed), textview);
    353 	if (prefs_common.enable_bgcolor) {
    354 		gtk_text_buffer_create_tag(buffer, "quote0",
    355 				"foreground-rgba", &quote_colors[0],
    356 				"paragraph-background-rgba", &quote_bgcolors[0],
    357 				NULL);
    358 		gtk_text_buffer_create_tag(buffer, "quote1",
    359 				"foreground-rgba", &quote_colors[1],
    360 				"paragraph-background-rgba", &quote_bgcolors[1],
    361 				NULL);
    362 		gtk_text_buffer_create_tag(buffer, "quote2",
    363 				"foreground-rgba", &quote_colors[2],
    364 				"paragraph-background-rgba", &quote_bgcolors[2],
    365 				NULL);
    366 	} else {
    367 		gtk_text_buffer_create_tag(buffer, "quote0",
    368 				"foreground-rgba", &quote_colors[0],
    369 				NULL);
    370 		gtk_text_buffer_create_tag(buffer, "quote1",
    371 				"foreground-rgba", &quote_colors[1],
    372 				NULL);
    373 		gtk_text_buffer_create_tag(buffer, "quote2",
    374 				"foreground-rgba", &quote_colors[2],
    375 				NULL);
    376 	}
    377 	gtk_text_buffer_create_tag(buffer, "tags",
    378 			"foreground-rgba", &tags_color,
    379 			"paragraph-background-rgba", &tags_bgcolor,
    380 			NULL);
    381 	gtk_text_buffer_create_tag(buffer, "emphasis",
    382 			"foreground-rgba", &emphasis_color,
    383 			NULL);
    384 	gtk_text_buffer_create_tag(buffer, "signature",
    385 			"foreground-rgba", &signature_color,
    386 			NULL);
    387 	tag = gtk_text_buffer_create_tag(buffer, "link",
    388 			"foreground-rgba", &uri_color,
    389 			NULL);
    390 	qtag = gtk_text_buffer_create_tag(buffer, "qlink",
    391 			NULL);
    392 	gtk_text_buffer_create_tag(buffer, "link-hover",
    393 			"underline", PANGO_UNDERLINE_SINGLE,
    394 			NULL);
    395 	gtk_text_buffer_create_tag(buffer, "diff-add",
    396 			"foreground-rgba", &diff_added_color,
    397 			NULL);
    398 	gtk_text_buffer_create_tag(buffer, "diff-del",
    399 			"foreground-rgba", &diff_deleted_color,
    400 			NULL);
    401 	gtk_text_buffer_create_tag(buffer, "diff-add-file",
    402 			"foreground-rgba", &diff_added_color,
    403 			"weight", PANGO_WEIGHT_BOLD,
    404 			NULL);
    405 	gtk_text_buffer_create_tag(buffer, "diff-del-file",
    406 			"foreground-rgba", &diff_deleted_color,
    407 			"weight", PANGO_WEIGHT_BOLD,
    408 			NULL);
    409 	gtk_text_buffer_create_tag(buffer, "diff-hunk",
    410 			"foreground-rgba", &diff_hunk_color,
    411 			"weight", PANGO_WEIGHT_BOLD,
    412 			NULL);
    413 	g_signal_connect(G_OBJECT(qtag), "event",
    414 				   G_CALLBACK(textview_uri_button_pressed), textview);
    415 	g_signal_connect(G_OBJECT(tag), "event",
    416 				   G_CALLBACK(textview_uri_button_pressed), textview);
    417 
    418 	pango_font_description_free(font_desc);
    419 	pango_font_description_free(bold_font_desc);
    420 }
    421 
    422 void textview_init(TextView *textview)
    423 {
    424 	if (!hand_cursor)
    425 		hand_cursor = gdk_cursor_new_for_display(
    426 				gtk_widget_get_display(textview->vbox), GDK_HAND2);
    427 	if (!text_cursor)
    428 		text_cursor = gdk_cursor_new_for_display(
    429 				gtk_widget_get_display(textview->vbox), GDK_XTERM);
    430 	if (!watch_cursor)
    431 		watch_cursor = gdk_cursor_new_for_display(
    432 				gtk_widget_get_display(textview->vbox), GDK_WATCH);
    433 
    434 	textview_reflect_prefs(textview);
    435 	textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
    436 }
    437 
    438 #define CHANGE_TAG_COLOR(tagname, colorfg, colorbg) { \
    439 	tag = gtk_text_tag_table_lookup(tags, tagname); \
    440 	if (tag) \
    441 		g_object_set(G_OBJECT(tag), "foreground-rgba", colorfg, "paragraph-background-rgba", colorbg, NULL); \
    442 }
    443 
    444 static void textview_update_message_colors(TextView *textview)
    445 {
    446 	GdkRGBA black = {0, 0, 0, 1};
    447 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
    448 
    449 	GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
    450 	GtkTextTag *tag = NULL;
    451 
    452 	quote_bgcolors[0] = quote_bgcolors[1] = quote_bgcolors[2] = black;
    453 	quote_colors[0] = quote_colors[1] = quote_colors[2] = black;
    454 	uri_color = emphasis_color = signature_color = diff_added_color =
    455 		diff_deleted_color = diff_hunk_color = black;
    456 	tags_bgcolor = tags_color = black;
    457 
    458 	if (prefs_common.enable_color) {
    459 		/* grab the quote colors, converting from an int to a GdkColor */
    460 		quote_colors[0] = prefs_common.color[COL_QUOTE_LEVEL1];
    461 		quote_colors[1] = prefs_common.color[COL_QUOTE_LEVEL2];
    462 		quote_colors[2] = prefs_common.color[COL_QUOTE_LEVEL3];
    463 		uri_color = prefs_common.color[COL_URI];
    464 		signature_color = prefs_common.color[COL_SIGNATURE];
    465 		emphasis_color = prefs_common.color[COL_EMPHASIS];
    466 		diff_added_color = prefs_common.color[COL_DIFF_ADDED];
    467 		diff_deleted_color = prefs_common.color[COL_DIFF_DELETED];
    468 		diff_hunk_color = prefs_common.color[COL_DIFF_HUNK];
    469 	}
    470 	if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
    471 		quote_bgcolors[0] = prefs_common.color[COL_QUOTE_LEVEL1_BG];
    472 		quote_bgcolors[1] = prefs_common.color[COL_QUOTE_LEVEL2_BG];
    473 		quote_bgcolors[2] = prefs_common.color[COL_QUOTE_LEVEL3_BG];
    474 		CHANGE_TAG_COLOR("quote0", &quote_colors[0], &quote_bgcolors[0]);
    475 		CHANGE_TAG_COLOR("quote1", &quote_colors[1], &quote_bgcolors[1]);
    476 		CHANGE_TAG_COLOR("quote2", &quote_colors[2], &quote_bgcolors[2]);
    477 	} else {
    478 		CHANGE_TAG_COLOR("quote0", &quote_colors[0], NULL);
    479 		CHANGE_TAG_COLOR("quote1", &quote_colors[1], NULL);
    480 		CHANGE_TAG_COLOR("quote2", &quote_colors[2], NULL);
    481 	}
    482 
    483 	CHANGE_TAG_COLOR("emphasis", &emphasis_color, NULL);
    484 	CHANGE_TAG_COLOR("signature", &signature_color, NULL);
    485 	CHANGE_TAG_COLOR("link", &uri_color, NULL);
    486 	CHANGE_TAG_COLOR("link-hover", &uri_color, NULL);
    487 	CHANGE_TAG_COLOR("diff-add", &diff_added_color, NULL);
    488 	CHANGE_TAG_COLOR("diff-del", &diff_deleted_color, NULL);
    489 	CHANGE_TAG_COLOR("diff-add-file", &diff_added_color, NULL);
    490 	CHANGE_TAG_COLOR("diff-del-file", &diff_deleted_color, NULL);
    491 	CHANGE_TAG_COLOR("diff-hunk", &diff_hunk_color, NULL);
    492 
    493 	tags_bgcolor = prefs_common.color[COL_TAGS_BG];
    494 	tags_color = prefs_common.color[COL_TAGS];
    495 }
    496 #undef CHANGE_TAG_COLOR
    497 
    498 void textview_reflect_prefs(TextView *textview)
    499 {
    500 	textview_update_message_colors(textview);
    501 }
    502 
    503 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
    504 {
    505 	textview->loading = TRUE;
    506 	textview->stop_loading = FALSE;
    507 
    508 	textview_clear(textview);
    509 
    510 	if (mimeinfo->type == MIMETYPE_MULTIPART ||
    511 	    (mimeinfo->type == MIMETYPE_MESSAGE && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
    512 		textview_add_parts(textview, mimeinfo);
    513 	} else {
    514 		if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
    515 			perror("fseek");
    516 		textview_write_body(textview, mimeinfo);
    517 	}
    518 
    519 	textview->loading = FALSE;
    520 	textview->stop_loading = FALSE;
    521 	textview_set_position(textview, 0);
    522 }
    523 
    524 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
    525 {
    526 	if (textview->stop_loading)
    527 		return;
    528 	if (mimeinfo->type == MIMETYPE_MULTIPART)
    529 		return;
    530 
    531 	gchar buf[BUFFSIZE];
    532 	GPtrArray *headers = NULL;
    533 	const gchar *name;
    534 	gchar *content_type;
    535 
    536 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
    537 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
    538 	int charcount = gtk_text_buffer_get_char_count(buffer);
    539 	GtkTextIter iter;
    540 	gtk_text_buffer_get_end_iter(buffer, &iter);
    541 
    542 	textview->prev_quote_level = -1;
    543 
    544 	if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
    545 		FILE *fp;
    546 		if (mimeinfo->content == MIMECONTENT_MEM)
    547 			fp = str_open_as_stream(mimeinfo->data.mem);
    548 		else
    549 			fp = g_fopen(mimeinfo->data.filename, "rb");
    550 		if (!fp) {
    551 			FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen");
    552 			return;
    553 		}
    554 		if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
    555 			FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
    556 			fclose(fp);
    557 			return;
    558 		}
    559 		headers = textview_scan_header(textview, fp);
    560 		if (headers) {
    561 			if (charcount > 0)
    562 				gtk_text_buffer_insert(buffer, &iter, "\n", 1);
    563 			textview_show_header(textview, headers);
    564 			procheader_header_array_destroy(headers);
    565 		}
    566 		fclose(fp);
    567 		return;
    568 	}
    569 
    570 	name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
    571 	content_type = procmime_get_content_type_str(mimeinfo->type,
    572 						     mimeinfo->subtype);
    573 	if (name == NULL)
    574 		name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
    575 	if (name != NULL)
    576 		g_snprintf(buf, sizeof(buf), _("[%s  %s (%ld bytes)]"),
    577 			   name, content_type, mimeinfo->length);
    578 	else
    579 		g_snprintf(buf, sizeof(buf), _("[%s (%ld bytes)]"),
    580 			   content_type, mimeinfo->length);
    581 
    582 	g_free(content_type);
    583 
    584 	if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT
    585 	|| (mimeinfo->disposition == DISPOSITIONTYPE_INLINE &&
    586 	    mimeinfo->type != MIMETYPE_TEXT)) {
    587 		gtk_text_buffer_insert(buffer, &iter, "\n", 1);
    588 		TEXTVIEW_INSERT_LINK(buf, "cm://select_attachment", mimeinfo);
    589 		gtk_text_buffer_insert(buffer, &iter, " \n", -1);
    590 	} else if (mimeinfo->type == MIMETYPE_TEXT) {
    591 		if (prefs_common.display_header && (charcount > 0))
    592 			gtk_text_buffer_insert(buffer, &iter, "\n", 1);
    593 
    594 		if (!gtk_text_buffer_get_mark(buffer, "body_start")) {
    595 			gtk_text_buffer_get_end_iter(buffer, &iter);
    596 			gtk_text_buffer_create_mark(buffer, "body_start", &iter, TRUE);
    597 		}
    598 
    599 		textview_write_body(textview, mimeinfo);
    600 
    601 		if (!gtk_text_buffer_get_mark(buffer, "body_end")) {
    602 			gtk_text_buffer_get_end_iter(buffer, &iter);
    603 			gtk_text_buffer_create_mark(buffer, "body_end", &iter, TRUE);
    604 		}
    605 	}
    606 }
    607 
    608 static void recursive_add_parts(TextView *textview, GNode *node)
    609 {
    610 	MimeInfo *mimeinfo = (MimeInfo *) node->data;
    611 	textview_add_part(textview, mimeinfo);
    612 	if (mimeinfo->type != MIMETYPE_MULTIPART && mimeinfo->type != MIMETYPE_MESSAGE) {
    613 		return;
    614 	}
    615 
    616 	GNode *iter;
    617 	if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") != 0) {
    618 		for (iter = g_node_first_child(node); iter != NULL; iter = g_node_next_sibling(iter)) {
    619 			recursive_add_parts(textview, iter);
    620 		}
    621 		return;
    622 	}
    623 
    624 	GNode *preferred_body = NULL;
    625 	int preferred_score = 0;
    626 	for (iter = g_node_first_child(node); iter != NULL; iter = g_node_next_sibling(iter)) {
    627 		int score = 1;
    628 		MimeInfo *submime = (MimeInfo *) iter->data;
    629 		if (submime->type == MIMETYPE_TEXT)
    630 			score++;
    631 		if (submime->subtype != NULL) {
    632 			if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
    633 				score++;
    634 		}
    635 		if (score > preferred_score) {
    636 			preferred_score = score;
    637 			preferred_body = iter;
    638 		}
    639 	}
    640 
    641 	if (preferred_body != NULL)
    642 		recursive_add_parts(textview, preferred_body);
    643 }
    644 
    645 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
    646 {
    647 	recursive_add_parts(textview, mimeinfo->node);
    648 }
    649 
    650 void textview_show_error(TextView *textview)
    651 {
    652 	textview_clear(textview);
    653 
    654 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
    655 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
    656 	GtkTextIter iter;
    657 	gtk_text_buffer_get_start_iter(buffer, &iter);
    658 
    659 	TEXTVIEW_INSERT(_("\n"
    660 		      "  This message can't be displayed.\n"
    661 		      "  This is probably due to a network error.\n"
    662 		      "\n"
    663 		      "  Use "));
    664 	TEXTVIEW_INSERT_LINK(_("'Network Log'"), "cm://view_log", NULL);
    665 	TEXTVIEW_INSERT(_(" in the Tools menu for more information."));
    666 	textview_show_icon(textview, "dialog-error");
    667 }
    668 
    669 void textview_show_info(TextView *textview, const gchar *info_str)
    670 {
    671 	textview_clear(textview);
    672 
    673 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
    674 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
    675 	GtkTextIter iter;
    676 	gtk_text_buffer_get_start_iter(buffer, &iter);
    677 
    678 	TEXTVIEW_INSERT(info_str);
    679 	textview_show_icon(textview, "dialog-information");
    680 	textview_cursor_normal(textview);
    681 }
    682 
    683 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
    684 {
    685 	if (!partinfo)
    686 		return;
    687 
    688 	GtkUIManager *ui_manager;
    689 	if (textview->messageview->window != NULL)
    690 		ui_manager = textview->messageview->ui_manager;
    691 	else
    692 		ui_manager = textview->messageview->mainwin->ui_manager;
    693 
    694 	textview_clear(textview);
    695 
    696 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
    697 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
    698 	GtkTextIter iter;
    699 	gtk_text_buffer_get_start_iter(buffer, &iter);
    700 
    701 	TEXTVIEW_INSERT("\n");
    702 
    703 	const char *name = procmime_mimeinfo_get_parameter(partinfo, "filename");
    704 	if (name == NULL)
    705 		name = procmime_mimeinfo_get_parameter(partinfo, "name");
    706 	if (name != NULL) {
    707 		char *content_type = procmime_get_content_type_str(partinfo->type,
    708 						     partinfo->subtype);
    709 		TEXTVIEW_INSERT("  ");
    710 		TEXTVIEW_INSERT_BOLD(name);
    711 		TEXTVIEW_INSERT(" (");
    712 		TEXTVIEW_INSERT(content_type);
    713 		TEXTVIEW_INSERT(", ");
    714 		TEXTVIEW_INSERT(to_human_readable((goffset)partinfo->length));
    715 		TEXTVIEW_INSERT("):\n\n");
    716 		free(content_type);
    717 	}
    718 	TEXTVIEW_INSERT(_("  The following can be performed on this part\n"));
    719 	TEXTVIEW_INSERT(_("  by right-clicking the icon or list item:"));
    720 	TEXTVIEW_INSERT("\n");
    721 
    722 	TEXTVIEW_INSERT(_("     - To save, select "));
    723 	TEXTVIEW_INSERT_LINK(_("'Save as...'"), "cm://save_as", NULL);
    724 	TEXTVIEW_INSERT("\n");
    725 
    726 	TEXTVIEW_INSERT(_("     - To display as text, select "));
    727 	TEXTVIEW_INSERT_LINK(_("'Display as text'"), "cm://display_as_text", NULL);
    728 
    729 	TEXTVIEW_INSERT(_("     - To open with an external program, select "));
    730 	TEXTVIEW_INSERT_LINK(_("'Open'"), "cm://open", NULL);
    731 	TEXTVIEW_INSERT(_("')\n"));
    732 	TEXTVIEW_INSERT(_("       (alternately double-click, or click the middle "));
    733 	TEXTVIEW_INSERT(_("mouse button)\n"));
    734 	TEXTVIEW_INSERT(_("     - Or use "));
    735 	TEXTVIEW_INSERT_LINK(_("'Open with...'"), "cm://open_with", NULL);
    736 	TEXTVIEW_INSERT("\n");
    737 
    738 	textview_show_icon(textview, "dialog-information");
    739 }
    740 
    741 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
    742 {
    743 	FILE *tmpfp;
    744 	gchar buf[BUFFSIZE];
    745 	CodeConverter *conv;
    746 	const gchar *charset;
    747 	const gchar *p, *cmd;
    748 	GSList *cur;
    749 	gboolean continue_write = TRUE;
    750 	glong wrote = 0, i = 0;
    751 	FolderItem *folder_item = NULL;
    752 
    753 	if (textview->messageview->forced_charset)
    754 		charset = textview->messageview->forced_charset;
    755 	else {
    756 		/* use supersets transparently when possible */
    757 		charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
    758 		if (charset && !strcasecmp(charset, CS_ISO_8859_1))
    759 			charset = CS_WINDOWS_1252;
    760 		else if (charset && !strcasecmp(charset, CS_X_GBK))
    761 			charset = CS_GB18030;
    762 		else if (charset && !strcasecmp(charset, CS_GBK))
    763 			charset = CS_GB18030;
    764 		else if (charset && !strcasecmp(charset, CS_GB2312))
    765 			charset = CS_GB18030;
    766 	}
    767 
    768 	conv = conv_code_converter_new(charset);
    769 
    770 	procmime_force_encoding(textview->messageview->forced_encoding);
    771 
    772 	textview->is_in_signature = FALSE;
    773 	textview->is_diff = FALSE;
    774 	textview->is_attachment = FALSE;
    775 	textview->is_in_git_patch = FALSE;
    776 
    777 	procmime_decode_content(mimeinfo);
    778 
    779 	if (textview->messageview->msginfo && textview->messageview->msginfo->folder)
    780 		folder_item = textview->messageview->msginfo->folder;
    781 
    782 	if (!g_ascii_strcasecmp(mimeinfo->subtype, "html") &&
    783 	    ((folder_item && folder_item->prefs && folder_item->prefs->render_html == HTML_RENDER_ALWAYS)
    784 		|| ((folder_item && folder_item->prefs && folder_item->prefs->render_html == HTML_RENDER_DEFAULT)
    785 			&& prefs_common.render_html))) {
    786 		gchar *filename;
    787 
    788 		filename = procmime_get_tmp_file_name(mimeinfo);
    789 		if (procmime_get_part(filename, mimeinfo) == 0) {
    790 			tmpfp = g_fopen(filename, "rb");
    791 			if (tmpfp) {
    792 				textview_show_html(textview, tmpfp, conv);
    793 				fclose(tmpfp);
    794 			}
    795 			unlink(filename);
    796 		}
    797 		g_free(filename);
    798 	} else {
    799 		if (!g_ascii_strcasecmp(mimeinfo->subtype, "x-patch")
    800 				|| !g_ascii_strcasecmp(mimeinfo->subtype, "x-diff"))
    801 			textview->is_diff = TRUE;
    802 
    803 		/* Displayed part is an attachment, but not an attached
    804 		 * e-mail. Set a flag, so that elsewhere in the code we
    805 		 * know not to try making collapsible quotes in it. */
    806 		if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT &&
    807 				mimeinfo->type != MIMETYPE_MESSAGE)
    808 			textview->is_attachment = TRUE;
    809 
    810 		if (mimeinfo->content == MIMECONTENT_MEM)
    811 			tmpfp = str_open_as_stream(mimeinfo->data.mem);
    812 		else
    813 			tmpfp = g_fopen(mimeinfo->data.filename, "rb");
    814 		if (!tmpfp) {
    815 			FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen");
    816 			conv_code_converter_destroy(conv);
    817 			return;
    818 		}
    819 		if (fseek(tmpfp, mimeinfo->offset, SEEK_SET) < 0) {
    820 			FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
    821 			fclose(tmpfp);
    822 			conv_code_converter_destroy(conv);
    823 			return;
    824 		}
    825 		debug_print("Viewing text content of type: %s (length: %ld)\n", mimeinfo->subtype, mimeinfo->length);
    826 		while (((i = ftell(tmpfp)) < mimeinfo->offset + mimeinfo->length) &&
    827 		       (fgets(buf, sizeof(buf), tmpfp) != NULL)
    828 		       && continue_write) {
    829 			textview_write_line(textview, buf, conv, TRUE);
    830 			if (textview->stop_loading) {
    831 				fclose(tmpfp);
    832 				conv_code_converter_destroy(conv);
    833 				return;
    834 			}
    835 			wrote += ftell(tmpfp)-i;
    836 			if (mimeinfo->length > 1024*1024
    837 			&&  wrote > 1024*1024
    838 			&& !textview->messageview->show_full_text) {
    839 				continue_write = FALSE;
    840 			}
    841 		}
    842 		fclose(tmpfp);
    843 	}
    844 
    845 	conv_code_converter_destroy(conv);
    846 	procmime_force_encoding(0);
    847 
    848 	textview->uri_list = g_slist_reverse(textview->uri_list);
    849 	for (cur = textview->uri_list; cur; cur = cur->next) {
    850 		ClickableText *uri = (ClickableText *)cur->data;
    851 		if (!uri->is_quote)
    852 			continue;
    853 		if (!prefs_common.hide_quotes ||
    854 		    uri->quote_level+1 < prefs_common.hide_quotes) {
    855 			textview_toggle_quote(textview, cur, uri, TRUE);
    856 			if (textview->stop_loading) {
    857 				return;
    858 			}
    859 		}
    860 	}
    861 
    862 	GTK_EVENTS_FLUSH();
    863 }
    864 
    865 static void textview_show_html(TextView *textview, FILE *fp,
    866 			       CodeConverter *conv)
    867 {
    868 	SC_HTMLParser *parser;
    869 	gchar *str;
    870 	gint lines = 0;
    871 
    872 	parser = sc_html_parser_new(fp, conv);
    873 	cm_return_if_fail(parser != NULL);
    874 
    875 	while ((str = sc_html_parse(parser)) != NULL) {
    876 	        if (parser->state == SC_HTML_HREF) {
    877 		        /* first time : get and copy the URL */
    878 		        if (parser->href == NULL) {
    879 				/* ALF - the claws html parser returns an empty string,
    880 				 * if still inside an <a>, but already parsed past HREF */
    881 				str = strtok(str, " ");
    882 				if (str) {
    883 					while (str && *str && g_ascii_isspace(*str))
    884 						str++;
    885 					parser->href = g_strdup(str);
    886 					/* the URL may (or not) be followed by the
    887 					 * referenced text */
    888 					str = strtok(NULL, "");
    889 				}
    890 		        }
    891 		        if (str != NULL)
    892 			        textview_write_link(textview, str, parser->href, NULL);
    893 	        } else
    894 		        textview_write_line(textview, str, NULL, FALSE);
    895 		lines++;
    896 		if (lines % 500 == 0)
    897 			GTK_EVENTS_FLUSH();
    898 		if (textview->stop_loading) {
    899 			sc_html_parser_destroy(parser);
    900 			return;
    901 		}
    902 	}
    903 	textview_write_line(textview, "\n", NULL, FALSE);
    904 
    905 	sc_html_parser_destroy(parser);
    906 }
    907 
    908 #define ADD_TXT_POS(bp_, ep_, pti_) \
    909 	if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
    910 		last = last->next; \
    911 		last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
    912 		last->next = NULL; \
    913 	} else { \
    914 		g_warning("alloc error scanning URIs"); \
    915 		gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
    916 							 linebuf, -1, \
    917 							 fg_tag, NULL); \
    918 		return; \
    919 	}
    920 
    921 #define ADD_TXT_POS_LATER(bp_, ep_, pti_) \
    922 	if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
    923 		last = last->next; \
    924 		last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
    925 		last->next = NULL; \
    926 	} else { \
    927 		g_warning("alloc error scanning URIs"); \
    928 	}
    929 
    930 /* textview_make_clickable_parts() - colorizes clickable parts */
    931 static void textview_make_clickable_parts(TextView *textview,
    932 					  const gchar *fg_tag,
    933 					  const gchar *uri_tag,
    934 					  const gchar *linebuf,
    935 					  gboolean hdr)
    936 {
    937 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
    938 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
    939 	GtkTextIter iter;
    940 	gchar *mybuf = g_strdup(linebuf);
    941 
    942 	/* parse table - in order of priority */
    943 	struct table {
    944 		const gchar *needle; /* token */
    945 
    946 		/* token search function */
    947 		gchar    *(*search)	(const gchar *haystack,
    948 					 const gchar *needle);
    949 		/* part parsing function */
    950 		gboolean  (*parse)	(const gchar *start,
    951 					 const gchar *scanpos,
    952 					 const gchar **bp_,
    953 					 const gchar **ep_,
    954 					 gboolean hdr);
    955 		/* part to URI function */
    956 		gchar    *(*build_uri)	(const gchar *bp,
    957 					 const gchar *ep);
    958 	};
    959 
    960 	static struct table parser[] = {
    961 		{"http://",  strcasestr, get_uri_part,   make_uri_string},
    962 		{"https://", strcasestr, get_uri_part,   make_uri_string},
    963 		{"ftp://",   strcasestr, get_uri_part,   make_uri_string},
    964 		{"ftps://",  strcasestr, get_uri_part,   make_uri_string},
    965 		{"sftp://",  strcasestr, get_uri_part,   make_uri_string},
    966 		{"gopher://",strcasestr, get_uri_part,   make_uri_string},
    967 		{"www.",     strcasestr, get_uri_part,   make_http_string},
    968 		{"webcal://",strcasestr, get_uri_part,   make_uri_string},
    969 		{"webcals://",strcasestr, get_uri_part,  make_uri_string},
    970 		{"mailto:",  strcasestr, get_uri_part,   make_uri_string},
    971 		{"@",        strcasestr, get_email_part, make_email_string}
    972 	};
    973 	const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
    974 
    975 	gint  n;
    976 	const gchar *walk, *bp, *ep;
    977 
    978 	struct txtpos {
    979 		const gchar	*bp, *ep;	/* text position */
    980 		gint		 pti;		/* index in parse table */
    981 		struct txtpos	*next;		/* next */
    982 	} head = {NULL, NULL, 0,  NULL}, *last = &head;
    983 
    984 	if (!g_utf8_validate(linebuf, -1, NULL)) {
    985 		g_free(mybuf);
    986 		mybuf = g_malloc(strlen(linebuf)*2 +1);
    987 		conv_localetodisp(mybuf, strlen(linebuf)*2 +1, linebuf);
    988 	}
    989 
    990 	gtk_text_buffer_get_end_iter(buffer, &iter);
    991 
    992 	/* parse for clickable parts, and build a list of begin and end positions  */
    993 	for (walk = mybuf;;) {
    994 		gint last_index = PARSE_ELEMS;
    995 		gchar *scanpos = NULL;
    996 
    997 		/* FIXME: this looks phony. scanning for anything in the parse table */
    998 		for (n = 0; n < PARSE_ELEMS; n++) {
    999 			gchar *tmp;
   1000 
   1001 			tmp = parser[n].search(walk, parser[n].needle);
   1002 			if (tmp) {
   1003 				if (scanpos == NULL || tmp < scanpos) {
   1004 					scanpos = tmp;
   1005 					last_index = n;
   1006 				}
   1007 			}
   1008 		}
   1009 
   1010 		if (scanpos) {
   1011 			/* check if URI can be parsed */
   1012 			if (parser[last_index].parse(walk, scanpos, &bp, &ep, hdr)
   1013 			    && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
   1014 					ADD_TXT_POS(bp, ep, last_index);
   1015 					walk = ep;
   1016 			} else
   1017 				walk = scanpos +
   1018 					strlen(parser[last_index].needle);
   1019 		} else
   1020 			break;
   1021 	}
   1022 
   1023 	/* colorize this line */
   1024 	if (head.next) {
   1025 		const gchar *normal_text = mybuf;
   1026 
   1027 		/* insert URIs */
   1028 		for (last = head.next; last != NULL;
   1029 		     normal_text = last->ep, last = last->next) {
   1030 			ClickableText *uri;
   1031 			uri = g_new0(ClickableText, 1);
   1032 			if (last->bp - normal_text > 0)
   1033 				gtk_text_buffer_insert_with_tags_by_name
   1034 					(buffer, &iter,
   1035 					 normal_text,
   1036 					 last->bp - normal_text,
   1037 					 fg_tag, NULL);
   1038 			uri->uri = parser[last->pti].build_uri(last->bp,
   1039 							       last->ep);
   1040 			uri->start = gtk_text_iter_get_offset(&iter);
   1041 			gtk_text_buffer_insert_with_tags_by_name
   1042 				(buffer, &iter, last->bp, last->ep - last->bp,
   1043 				 uri_tag, fg_tag, NULL);
   1044 			uri->end = gtk_text_iter_get_offset(&iter);
   1045 			uri->filename = NULL;
   1046 			textview->uri_list =
   1047 				g_slist_prepend(textview->uri_list, uri);
   1048 		}
   1049 
   1050 		if (*normal_text)
   1051 			gtk_text_buffer_insert_with_tags_by_name
   1052 				(buffer, &iter, normal_text, -1, fg_tag, NULL);
   1053 	} else {
   1054 		gtk_text_buffer_insert_with_tags_by_name
   1055 			(buffer, &iter, mybuf, -1, fg_tag, NULL);
   1056 	}
   1057 	g_free(mybuf);
   1058 }
   1059 
   1060 /* textview_make_clickable_parts() - colorizes clickable parts */
   1061 static void textview_make_clickable_parts_later(TextView *textview,
   1062 					  gint start, gint end)
   1063 {
   1064 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
   1065 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
   1066 	GtkTextIter start_iter, end_iter;
   1067 	gchar *mybuf;
   1068 	gint offset = 0;
   1069 	/* parse table - in order of priority */
   1070 	struct table {
   1071 		const gchar *needle; /* token */
   1072 
   1073 		/* token search function */
   1074 		gchar    *(*search)	(const gchar *haystack,
   1075 					 const gchar *needle);
   1076 		/* part parsing function */
   1077 		gboolean  (*parse)	(const gchar *start,
   1078 					 const gchar *scanpos,
   1079 					 const gchar **bp_,
   1080 					 const gchar **ep_,
   1081 					 gboolean hdr);
   1082 		/* part to URI function */
   1083 		gchar    *(*build_uri)	(const gchar *bp,
   1084 					 const gchar *ep);
   1085 	};
   1086 
   1087 	static struct table parser[] = {
   1088 		{"http://",  strcasestr, get_uri_part,   make_uri_string},
   1089 		{"https://", strcasestr, get_uri_part,   make_uri_string},
   1090 		{"ftp://",   strcasestr, get_uri_part,   make_uri_string},
   1091 		{"ftps://",  strcasestr, get_uri_part,   make_uri_string},
   1092 		{"sftp://",  strcasestr, get_uri_part,   make_uri_string},
   1093 		{"www.",     strcasestr, get_uri_part,   make_http_string},
   1094 		{"mailto:",  strcasestr, get_uri_part,   make_uri_string},
   1095 		{"webcal://",strcasestr, get_uri_part,   make_uri_string},
   1096 		{"webcals://",strcasestr, get_uri_part,  make_uri_string},
   1097 		{"@",        strcasestr, get_email_part, make_email_string}
   1098 	};
   1099 	const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
   1100 
   1101 	gint  n;
   1102 	const gchar *walk, *bp, *ep;
   1103 
   1104 	struct txtpos {
   1105 		const gchar	*bp, *ep;	/* text position */
   1106 		gint		 pti;		/* index in parse table */
   1107 		struct txtpos	*next;		/* next */
   1108 	} head = {NULL, NULL, 0,  NULL}, *last = &head;
   1109 
   1110 	gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
   1111 	gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
   1112 	mybuf = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE);
   1113 	offset = gtk_text_iter_get_offset(&start_iter);
   1114 
   1115 	/* parse for clickable parts, and build a list of begin and end positions  */
   1116 	for (walk = mybuf;;) {
   1117 		gint last_index = PARSE_ELEMS;
   1118 		gchar *scanpos = NULL;
   1119 
   1120 		/* FIXME: this looks phony. scanning for anything in the parse table */
   1121 		for (n = 0; n < PARSE_ELEMS; n++) {
   1122 			gchar *tmp;
   1123 
   1124 			tmp = parser[n].search(walk, parser[n].needle);
   1125 			if (tmp) {
   1126 				if (scanpos == NULL || tmp < scanpos) {
   1127 					scanpos = tmp;
   1128 					last_index = n;
   1129 				}
   1130 			}
   1131 		}
   1132 
   1133 		if (scanpos) {
   1134 			/* check if URI can be parsed */
   1135 			if (parser[last_index].parse(walk, scanpos, &bp, &ep, FALSE)
   1136 			    && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
   1137 					ADD_TXT_POS_LATER(bp, ep, last_index);
   1138 					walk = ep;
   1139 			} else
   1140 				walk = scanpos +
   1141 					strlen(parser[last_index].needle);
   1142 		} else
   1143 			break;
   1144 	}
   1145 
   1146 	/* colorize this line */
   1147 	if (head.next) {
   1148 		/* insert URIs */
   1149 		for (last = head.next; last != NULL; last = last->next) {
   1150 			ClickableText *uri;
   1151 			gint start_offset, end_offset;
   1152 			gchar *tmp_str;
   1153 			gchar old_char;
   1154 			uri = g_new0(ClickableText, 1);
   1155 			uri->uri = parser[last->pti].build_uri(last->bp,
   1156 							       last->ep);
   1157 
   1158 			tmp_str = mybuf;
   1159 			old_char = tmp_str[last->ep - mybuf];
   1160 			tmp_str[last->ep - mybuf] = '\0';
   1161 			end_offset = g_utf8_strlen(tmp_str, -1);
   1162 			tmp_str[last->ep - mybuf] = old_char;
   1163 
   1164 			old_char = tmp_str[last->bp - mybuf];
   1165 			tmp_str[last->bp - mybuf] = '\0';
   1166 			start_offset = g_utf8_strlen(tmp_str, -1);
   1167 			tmp_str[last->bp - mybuf] = old_char;
   1168 
   1169 			gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start_offset + offset);
   1170 			gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end_offset + offset);
   1171 
   1172 			uri->start = gtk_text_iter_get_offset(&start_iter);
   1173 
   1174 			gtk_text_buffer_apply_tag_by_name(buffer, "link", &start_iter, &end_iter);
   1175 
   1176 			uri->end = gtk_text_iter_get_offset(&end_iter);
   1177 			uri->filename = NULL;
   1178 			textview->uri_list =
   1179 				g_slist_prepend(textview->uri_list, uri);
   1180 		}
   1181 	}
   1182 
   1183 	g_free(mybuf);
   1184 }
   1185 
   1186 #undef ADD_TXT_POS
   1187 
   1188 static void textview_write_line(TextView *textview, const gchar *str,
   1189 				CodeConverter *conv, gboolean do_quote_folding)
   1190 {
   1191 	GtkTextView *text;
   1192 	GtkTextBuffer *buffer;
   1193 	GtkTextIter iter;
   1194 	gchar buf[BUFFSIZE];
   1195 	gchar *fg_color;
   1196 	gint quotelevel = -1, real_quotelevel = -1;
   1197 	gchar quote_tag_str[10];
   1198 
   1199 	text = GTK_TEXT_VIEW(textview->text);
   1200 	buffer = gtk_text_view_get_buffer(text);
   1201 	gtk_text_buffer_get_end_iter(buffer, &iter);
   1202 
   1203 	if (!conv)
   1204 		strncpy2(buf, str, sizeof(buf));
   1205 	else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
   1206 		conv_localetodisp(buf, sizeof(buf), str);
   1207 
   1208 	strcrchomp(buf);
   1209 	fg_color = NULL;
   1210 
   1211 	/* change color of quotation
   1212 	   >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
   1213 	   Up to 3 levels of quotations are detected, and each
   1214 	   level is colored using a different color. */
   1215 	if (prefs_common.enable_color && !textview->is_attachment && line_has_quote_char(buf)) {
   1216 		real_quotelevel = quote_depth(buf);
   1217 		quotelevel = real_quotelevel;
   1218 		/* set up the correct foreground color */
   1219 		if (quotelevel > 2) {
   1220 			/* recycle colors */
   1221 			if (prefs_common.recycle_quote_colors)
   1222 				quotelevel %= 3;
   1223 			else
   1224 				quotelevel = 2;
   1225 		}
   1226 	}
   1227 
   1228 	if (quotelevel == -1)
   1229 		fg_color = NULL;
   1230 	else {
   1231 		g_snprintf(quote_tag_str, sizeof(quote_tag_str),
   1232 			   "quote%d", quotelevel);
   1233 		fg_color = quote_tag_str;
   1234 	}
   1235 
   1236 	if (prefs_common.enable_color) {
   1237 		if (textview->is_diff || textview->is_in_git_patch) {
   1238 			if (strncmp(buf, "+++ ", 4) == 0)
   1239 				fg_color = "diff-add-file";
   1240 			else if (buf[0] == '+')
   1241 				fg_color = "diff-add";
   1242 			else if (strncmp(buf, "--- ", 4) == 0)
   1243 				fg_color = "diff-del-file";
   1244 			else if (buf[0] == '-')
   1245 				fg_color = "diff-del";
   1246 			else if (strncmp(buf, "@@ ", 3) == 0 &&
   1247 				 strstr(&buf[3], " @@"))
   1248 				fg_color = "diff-hunk";
   1249 		} else if (strncmp(buf, "diff --git ", 11) == 0) {
   1250 			textview->is_in_git_patch = TRUE;
   1251 		}
   1252 	}
   1253 
   1254 	if (!textview->is_attachment && real_quotelevel > -1 && do_quote_folding) {
   1255 		if (!g_utf8_validate(buf, -1, NULL)) {
   1256 			gchar *utf8buf = NULL;
   1257 			utf8buf = g_malloc(BUFFSIZE);
   1258 			conv_localetodisp(utf8buf, BUFFSIZE, buf);
   1259 			strncpy2(buf, utf8buf, BUFFSIZE-1);
   1260 			g_free(utf8buf);
   1261 		}
   1262 do_quote:
   1263 		if ( textview->prev_quote_level != real_quotelevel ) {
   1264 			ClickableText *uri;
   1265 			uri = g_new0(ClickableText, 1);
   1266 			uri->uri = g_strdup("");
   1267 			uri->data = g_strdup(buf);
   1268 			uri->data_len = strlen(uri->data);
   1269 			uri->start = gtk_text_iter_get_offset(&iter);
   1270 			uri->is_quote = TRUE;
   1271 			uri->quote_level = real_quotelevel;
   1272 			uri->fg_color = g_strdup(fg_color);
   1273 
   1274 			gtk_text_buffer_insert_with_tags_by_name
   1275 					(buffer, &iter, " [...]", -1,
   1276 					 "qlink", fg_color, NULL);
   1277 			uri->end = gtk_text_iter_get_offset(&iter);
   1278 			gtk_text_buffer_insert(buffer, &iter, "  \n", -1);
   1279 
   1280 			uri->filename = NULL;
   1281 			textview->uri_list =
   1282 				g_slist_prepend(textview->uri_list, uri);
   1283 
   1284 			textview->prev_quote_level = real_quotelevel;
   1285 		} else {
   1286 			GSList *last = textview->uri_list;
   1287 			ClickableText *lasturi = NULL;
   1288 			gint e_len = 0, n_len = 0;
   1289 
   1290 			if (textview->uri_list) {
   1291 				lasturi = (ClickableText *)last->data;
   1292 			} else {
   1293 				g_print("oops (%d %d)\n",
   1294 					real_quotelevel, textview->prev_quote_level);
   1295 			}
   1296 			if (lasturi) {
   1297 				if (lasturi->is_quote == FALSE) {
   1298 					textview->prev_quote_level = -1;
   1299 					goto do_quote;
   1300 				}
   1301 				e_len = lasturi->data ? lasturi->data_len:0;
   1302 				n_len = strlen(buf);
   1303 				lasturi->data = g_realloc((gchar *)lasturi->data, e_len + n_len + 1);
   1304 				strcpy((gchar *)lasturi->data + e_len, buf);
   1305 				*((gchar *)lasturi->data + e_len + n_len) = '\0';
   1306 				lasturi->data_len += n_len;
   1307 			}
   1308 		}
   1309 	} else {
   1310 		textview_make_clickable_parts(textview, fg_color, "link", buf, FALSE);
   1311 		textview->prev_quote_level = -1;
   1312 	}
   1313 }
   1314 
   1315 void textview_write_link(TextView *textview, const gchar *str,
   1316 			 const gchar *uri, CodeConverter *conv)
   1317 {
   1318 	GtkTextView *text;
   1319 	GtkTextBuffer *buffer;
   1320 	GtkTextIter iter;
   1321 	gchar buf[BUFFSIZE];
   1322 	gchar *bufp;
   1323 	ClickableText *r_uri;
   1324 
   1325 	if (!str || *str == '\0')
   1326 		return;
   1327 	if (!uri)
   1328 		return;
   1329 
   1330 	while (uri && *uri && g_ascii_isspace(*uri))
   1331 		uri++;
   1332 
   1333 	text = GTK_TEXT_VIEW(textview->text);
   1334 	buffer = gtk_text_view_get_buffer(text);
   1335 	gtk_text_buffer_get_end_iter(buffer, &iter);
   1336 
   1337 	if (!conv)
   1338 		strncpy2(buf, str, sizeof(buf));
   1339 	else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
   1340 		conv_utf8todisp(buf, sizeof(buf), str);
   1341 
   1342 	if (g_utf8_validate(buf, -1, NULL) == FALSE)
   1343 		return;
   1344 
   1345 	strcrchomp(buf);
   1346 
   1347 	gtk_text_buffer_get_end_iter(buffer, &iter);
   1348 	for (bufp = buf; *bufp != '\0'; bufp = g_utf8_next_char(bufp)) {
   1349 		gunichar ch;
   1350 
   1351 		ch = g_utf8_get_char(bufp);
   1352 		if (!g_unichar_isspace(ch))
   1353 			break;
   1354 	}
   1355 	if (bufp > buf)
   1356 		gtk_text_buffer_insert(buffer, &iter, buf, bufp - buf);
   1357 
   1358 	r_uri = g_new0(ClickableText, 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_prepend(textview->uri_list, r_uri);
   1366 }
   1367 
   1368 static void textview_set_cursor(GdkWindow *window, GdkCursor *cursor)
   1369 {
   1370 	if (GDK_IS_WINDOW(window))
   1371 		gdk_window_set_cursor(window, cursor);
   1372 }
   1373 void textview_clear(TextView *textview)
   1374 {
   1375 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
   1376 	GtkTextBuffer *buffer;
   1377 	GdkWindow *window = gtk_text_view_get_window(text,
   1378 				GTK_TEXT_WINDOW_TEXT);
   1379 
   1380 	buffer = gtk_text_view_get_buffer(text);
   1381 	gtk_text_buffer_set_text(buffer, "", -1);
   1382 	if (gtk_text_buffer_get_mark(buffer, "body_start"))
   1383 		gtk_text_buffer_delete_mark_by_name(buffer, "body_start");
   1384 	if (gtk_text_buffer_get_mark(buffer, "body_end"))
   1385 		gtk_text_buffer_delete_mark_by_name(buffer, "body_end");
   1386 
   1387 	TEXTVIEW_STATUSBAR_POP(textview);
   1388 	textview_uri_list_remove_all(textview->uri_list);
   1389 	textview->uri_list = NULL;
   1390 	textview->uri_hover = NULL;
   1391 	textview->prev_quote_level = -1;
   1392 
   1393 	textview->body_pos = 0;
   1394 
   1395 	if (textview->messageview->mainwin->cursor_count == 0) {
   1396 		textview_set_cursor(window, text_cursor);
   1397 	} else {
   1398 		textview_set_cursor(window, watch_cursor);
   1399 	}
   1400 }
   1401 
   1402 void textview_destroy(TextView *textview)
   1403 {
   1404 	GtkTextBuffer *buffer;
   1405 	GtkClipboard *clipboard;
   1406 
   1407 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
   1408 	clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
   1409 	gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
   1410 
   1411 	textview_uri_list_remove_all(textview->uri_list);
   1412 	textview->uri_list = NULL;
   1413 	textview->prev_quote_level = -1;
   1414 
   1415 	g_free(textview);
   1416 }
   1417 
   1418 void textview_set_text(TextView *textview, const gchar *text)
   1419 {
   1420 	GtkTextView *view;
   1421 	GtkTextBuffer *buffer;
   1422 
   1423 	cm_return_if_fail(textview != NULL);
   1424 	cm_return_if_fail(text != NULL);
   1425 
   1426 	textview_clear(textview);
   1427 
   1428 	view = GTK_TEXT_VIEW(textview->text);
   1429 	buffer = gtk_text_view_get_buffer(view);
   1430 	gtk_text_buffer_set_text(buffer, text, strlen(text));
   1431 }
   1432 
   1433 enum
   1434 {
   1435 	H_DATE		= 0,
   1436 	H_FROM		= 1,
   1437 	H_TO		= 2,
   1438 	H_NEWSGROUPS	= 3,
   1439 	H_SUBJECT	= 4,
   1440 	H_CC		= 5,
   1441 	H_REPLY_TO	= 6,
   1442 	H_FOLLOWUP_TO	= 7,
   1443 	H_X_MAILER	= 8,
   1444 	H_X_NEWSREADER	= 9,
   1445 	H_USER_AGENT	= 10,
   1446 	H_ORGANIZATION	= 11,
   1447 };
   1448 
   1449 void textview_set_position(TextView *textview, gint pos)
   1450 {
   1451 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
   1452 
   1453 	gtkut_text_view_set_position(text, pos);
   1454 }
   1455 
   1456 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
   1457 {
   1458 	gchar buf[BUFFSIZE];
   1459 	GPtrArray *headers, *sorted_headers;
   1460 	GSList *disphdr_list;
   1461 	Header *header;
   1462 	gint i;
   1463 
   1464 	cm_return_val_if_fail(fp != NULL, NULL);
   1465 
   1466 	if (prefs_common.show_all_headers) {
   1467 		headers = procheader_get_header_array(fp);
   1468 		sorted_headers = g_ptr_array_new();
   1469 		for (i = 0; i < headers->len; i++) {
   1470 			header = g_ptr_array_index(headers, i);
   1471 			if (!procheader_header_is_internal(header->name))
   1472 				g_ptr_array_add(sorted_headers, header);
   1473 			else
   1474 				procheader_header_free(header);
   1475 		}
   1476 		g_ptr_array_free(headers, TRUE);
   1477 		return sorted_headers;
   1478 	}
   1479 
   1480 	if (!prefs_common.display_header) {
   1481 		while (fgets(buf, sizeof(buf), fp) != NULL)
   1482 			if (buf[0] == '\r' || buf[0] == '\n') break;
   1483 		return NULL;
   1484 	}
   1485 
   1486 	headers = procheader_get_header_array(fp);
   1487 
   1488 	sorted_headers = g_ptr_array_new();
   1489 
   1490 	for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
   1491 	     disphdr_list = disphdr_list->next) {
   1492 		DisplayHeaderProp *dp =
   1493 			(DisplayHeaderProp *)disphdr_list->data;
   1494 
   1495 		for (i = 0; i < headers->len; i++) {
   1496 			header = g_ptr_array_index(headers, i);
   1497 			if (procheader_headername_equal(header->name,
   1498 							dp->name)) {
   1499 				if (dp->hidden)
   1500 					procheader_header_free(header);
   1501 				else
   1502 					g_ptr_array_add(sorted_headers, header);
   1503 
   1504 				g_ptr_array_remove_index(headers, i);
   1505 				i--;
   1506 			}
   1507 		}
   1508 	}
   1509 
   1510 	if (prefs_common.show_other_header) {
   1511 		for (i = 0; i < headers->len; i++) {
   1512 			header = g_ptr_array_index(headers, i);
   1513 			unfold_line(header->body);
   1514 			if (!procheader_header_is_internal(header->name)) {
   1515 				g_ptr_array_add(sorted_headers, header);
   1516 			} else {
   1517 				procheader_header_free(header);
   1518 			}
   1519 		}
   1520 		g_ptr_array_free(headers, TRUE);
   1521 	} else
   1522 		procheader_header_array_destroy(headers);
   1523 
   1524 
   1525 	return sorted_headers;
   1526 }
   1527 
   1528 void textview_show_icon(TextView *textview, const gchar *stock_id)
   1529 {
   1530 	GtkAllocation allocation;
   1531 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
   1532 	gint x, wx, wy;
   1533 	gchar *img_sym = NULL;
   1534 
   1535 	if (textview->image)
   1536 		gtk_widget_destroy(textview->image);
   1537 
   1538 	img_sym = g_strconcat(stock_id, "-symbolic", NULL);
   1539 	textview->image = gtk_image_new_from_icon_name(img_sym, GTK_ICON_SIZE_DIALOG);
   1540 	g_free(img_sym);
   1541 	cm_return_if_fail(textview->image != NULL);
   1542 
   1543 	gtk_widget_set_name(GTK_WIDGET(textview->image), "textview_icon");
   1544 	gtk_widget_show(textview->image);
   1545 
   1546 	gtk_widget_get_allocation(textview->text, &allocation);
   1547 	x = allocation.width - WIDTH - 5;
   1548 
   1549 	gtk_text_view_buffer_to_window_coords(
   1550 		GTK_TEXT_VIEW(textview->text),
   1551 		GTK_TEXT_WINDOW_TEXT, x, 5, &wx, &wy);
   1552 
   1553 	gtk_text_view_add_child_in_window(text, textview->image,
   1554 		GTK_TEXT_WINDOW_TEXT, wx, wy);
   1555 
   1556 	gtk_widget_show_all(textview->text);
   1557 	return;
   1558 }
   1559 
   1560 static void textview_show_header(TextView *textview, GPtrArray *headers)
   1561 {
   1562 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
   1563 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
   1564 	GtkTextIter iter;
   1565 	Header *header;
   1566 	gint i;
   1567 
   1568 	cm_return_if_fail(headers != NULL);
   1569 
   1570 	for (i = 0; i < headers->len; i++) {
   1571 		header = g_ptr_array_index(headers, i);
   1572 		cm_return_if_fail(header->name != NULL);
   1573 
   1574 		gtk_text_buffer_get_end_iter(buffer, &iter);
   1575 		gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, header->name, -1, "header_title", "header", NULL);
   1576 		if (header->name[strlen(header->name) - 1] != ' ')
   1577 			gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, " ", 1, "header_title", "header", NULL);
   1578 
   1579 		if (procheader_headername_equal(header->name, "Subject") ||
   1580 		    procheader_headername_equal(header->name, "From") ||
   1581 		    procheader_headername_equal(header->name, "To") ||
   1582 		    procheader_headername_equal(header->name, "Cc") ||
   1583 		    procheader_headername_equal(header->name, "Bcc") ||
   1584 		    procheader_headername_equal(header->name, "Reply-To") ||
   1585 		    procheader_headername_equal(header->name, "Sender") ||
   1586 		    procheader_headername_equal(header->name, "Resent-From") ||
   1587 		    procheader_headername_equal(header->name, "Resent-To"))
   1588 			unfold_line(header->body);
   1589 
   1590 		if (procheader_headername_equal(header->name, "Date") &&
   1591 		    prefs_common.msgview_date_format) {
   1592 			gchar hbody[80];
   1593 			procheader_date_parse(hbody, header->body, sizeof(hbody));
   1594 			gtk_text_buffer_get_end_iter (buffer, &iter);
   1595 			gtk_text_buffer_insert_with_tags_by_name
   1596 				(buffer, &iter, hbody, -1, "header", NULL);
   1597 		} else {
   1598 			gboolean hdr =
   1599 			  procheader_headername_equal(header->name, "From") ||
   1600 			  procheader_headername_equal(header->name, "To") ||
   1601 			  procheader_headername_equal(header->name, "Cc") ||
   1602 			  procheader_headername_equal(header->name, "Bcc") ||
   1603 			  procheader_headername_equal(header->name, "Reply-To") ||
   1604 			  procheader_headername_equal(header->name, "Sender") ||
   1605 			  procheader_headername_equal(header->name, "Resent-From") ||
   1606 			  procheader_headername_equal(header->name, "Resent-To");
   1607 			textview_make_clickable_parts(textview, "header",
   1608 						      "hlink", header->body,
   1609 						      hdr);
   1610 		}
   1611 		gtk_text_buffer_get_end_iter (buffer, &iter);
   1612 		gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
   1613 							 "header", NULL);
   1614 	}
   1615 }
   1616 
   1617 gboolean textview_search_string(TextView *textview, const gchar *str,
   1618 				gboolean case_sens)
   1619 {
   1620 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
   1621 
   1622 	return gtkut_text_view_search_string(text, str, case_sens);
   1623 }
   1624 
   1625 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
   1626 					 gboolean case_sens)
   1627 {
   1628 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
   1629 
   1630 	return gtkut_text_view_search_string_backward(text, str, case_sens);
   1631 }
   1632 
   1633 static gboolean textview_motion_notify(GtkWidget *widget,
   1634 				       GdkEventMotion *event,
   1635 				       TextView *textview)
   1636 {
   1637 	if (textview->loading)
   1638 		return FALSE;
   1639 	textview_uri_update(textview, event->x, event->y);
   1640 
   1641 	return FALSE;
   1642 }
   1643 
   1644 static gboolean textview_leave_notify(GtkWidget *widget,
   1645 				      GdkEventCrossing *event,
   1646 				      TextView *textview)
   1647 {
   1648 	if (textview->loading)
   1649 		return FALSE;
   1650 	textview_uri_update(textview, -1, -1);
   1651 
   1652 	return FALSE;
   1653 }
   1654 
   1655 static gboolean textview_visibility_notify(GtkWidget *widget,
   1656 					   GdkEventVisibility *event,
   1657 					   TextView *textview)
   1658 {
   1659 	gint wx, wy;
   1660 	GdkWindow *window;
   1661 	GdkDisplay *display;
   1662 	GdkSeat *seat;
   1663 
   1664 	if (textview->loading)
   1665 		return FALSE;
   1666 
   1667 	window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
   1668 					  GTK_TEXT_WINDOW_TEXT);
   1669 
   1670 	/* check if occurred for the text window part */
   1671 	if (window != event->window)
   1672 		return FALSE;
   1673 
   1674 	display = gdk_window_get_display(window);
   1675 	seat = gdk_display_get_default_seat(display);
   1676 	gdk_device_get_position(gdk_seat_get_pointer(seat),
   1677 			NULL, &wx, &wy);
   1678 	textview_uri_update(textview, wx, wy);
   1679 
   1680 	return FALSE;
   1681 }
   1682 
   1683 void textview_cursor_wait(TextView *textview)
   1684 {
   1685 	GdkWindow *window = gtk_text_view_get_window(
   1686 			GTK_TEXT_VIEW(textview->text),
   1687 			GTK_TEXT_WINDOW_TEXT);
   1688 	textview_set_cursor(window, watch_cursor);
   1689 }
   1690 
   1691 void textview_cursor_normal(TextView *textview)
   1692 {
   1693 	GdkWindow *window = gtk_text_view_get_window(
   1694 			GTK_TEXT_VIEW(textview->text),
   1695 			GTK_TEXT_WINDOW_TEXT);
   1696 	textview_set_cursor(window, text_cursor);
   1697 }
   1698 
   1699 static void textview_uri_update(TextView *textview, gint x, gint y)
   1700 {
   1701 	GtkTextBuffer *buffer;
   1702 	GtkTextIter start_iter, end_iter;
   1703 	ClickableText *uri = NULL;
   1704 
   1705 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
   1706 
   1707 	if (x != -1 && y != -1) {
   1708 		gint bx, by;
   1709 		GtkTextIter iter;
   1710 		GSList *tags;
   1711 		GSList *cur;
   1712 
   1713 		gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
   1714 						      GTK_TEXT_WINDOW_WIDGET,
   1715 						      x, y, &bx, &by);
   1716 		gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
   1717 						   &iter, bx, by);
   1718 
   1719 		tags = gtk_text_iter_get_tags(&iter);
   1720 		for (cur = tags; cur != NULL; cur = cur->next) {
   1721 			GtkTextTag *tag = cur->data;
   1722 			char *name;
   1723 
   1724 			g_object_get(G_OBJECT(tag), "name", &name, NULL);
   1725 
   1726 			if ((!strcmp(name, "link") || !strcmp(name, "hlink"))
   1727 			    && textview_get_uri_range(textview, &iter, tag,
   1728 						      &start_iter, &end_iter)) {
   1729 
   1730 				uri = textview_get_uri_from_range(textview,
   1731 								  &iter, tag,
   1732 								  &start_iter,
   1733 								  &end_iter);
   1734 			}
   1735 			g_free(name);
   1736 			if (uri)
   1737 				break;
   1738 		}
   1739 		g_slist_free(tags);
   1740 	}
   1741 
   1742 	if (uri != textview->uri_hover) {
   1743 		GdkWindow *window;
   1744 
   1745 		if (textview->uri_hover)
   1746 			gtk_text_buffer_remove_tag_by_name(buffer,
   1747 							   "link-hover",
   1748 							   &textview->uri_hover_start_iter,
   1749 							   &textview->uri_hover_end_iter);
   1750 
   1751 		textview->uri_hover = uri;
   1752 		if (uri) {
   1753 			textview->uri_hover_start_iter = start_iter;
   1754 			textview->uri_hover_end_iter = end_iter;
   1755 		}
   1756 
   1757 		window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
   1758 						  GTK_TEXT_WINDOW_TEXT);
   1759 		if (textview->messageview->mainwin->cursor_count == 0) {
   1760 			textview_set_cursor(window, uri ? hand_cursor : text_cursor);
   1761 		} else {
   1762 			textview_set_cursor(window, watch_cursor);
   1763 		}
   1764 
   1765 		TEXTVIEW_STATUSBAR_POP(textview);
   1766 
   1767 		if (uri) {
   1768 			if (!uri->is_quote)
   1769 				gtk_text_buffer_apply_tag_by_name(buffer,
   1770 							  "link-hover",
   1771 							  &start_iter,
   1772 							  &end_iter);
   1773 			TEXTVIEW_STATUSBAR_PUSH(textview, uri->uri);
   1774 		}
   1775 	}
   1776 }
   1777 
   1778 static gboolean textview_get_uri_range(TextView *textview,
   1779 				       GtkTextIter *iter,
   1780 				       GtkTextTag *tag,
   1781 				       GtkTextIter *start_iter,
   1782 				       GtkTextIter *end_iter)
   1783 {
   1784 	return get_tag_range(iter, tag, start_iter, end_iter);
   1785 }
   1786 
   1787 static ClickableText *textview_get_uri_from_range(TextView *textview,
   1788 					      GtkTextIter *iter,
   1789 					      GtkTextTag *tag,
   1790 					      GtkTextIter *start_iter,
   1791 					      GtkTextIter *end_iter)
   1792 {
   1793 	gint start_pos, end_pos, cur_pos;
   1794 	ClickableText *uri = NULL;
   1795 	GSList *cur;
   1796 
   1797 	start_pos = gtk_text_iter_get_offset(start_iter);
   1798 	end_pos = gtk_text_iter_get_offset(end_iter);
   1799 	cur_pos = gtk_text_iter_get_offset(iter);
   1800 
   1801 	for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
   1802 		ClickableText *uri_ = (ClickableText *)cur->data;
   1803 		if (start_pos == uri_->start &&
   1804 		    end_pos ==  uri_->end) {
   1805 			uri = uri_;
   1806 			break;
   1807 		}
   1808 	}
   1809 	for (cur = textview->uri_list; uri == NULL && cur != NULL; cur = cur->next) {
   1810 		ClickableText *uri_ = (ClickableText *)cur->data;
   1811 		if (start_pos == uri_->start ||
   1812 			   end_pos == uri_->end) {
   1813 			/* in case of contiguous links, textview_get_uri_range
   1814 			 * returns a broader range (start of 1st link to end
   1815 			 * of last link).
   1816 			 * In that case, correct link is the one covering
   1817 			 * current iter.
   1818 			 */
   1819 			if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
   1820 				uri = uri_;
   1821 				break;
   1822 			}
   1823 		}
   1824 	}
   1825 
   1826 	return uri;
   1827 }
   1828 
   1829 static ClickableText *textview_get_uri(TextView *textview,
   1830 				   GtkTextIter *iter,
   1831 				   GtkTextTag *tag)
   1832 {
   1833 	GtkTextIter start_iter, end_iter;
   1834 	ClickableText *uri = NULL;
   1835 
   1836 	if (textview_get_uri_range(textview, iter, tag, &start_iter,
   1837 				   &end_iter))
   1838 		uri = textview_get_uri_from_range(textview, iter, tag,
   1839 						  &start_iter, &end_iter);
   1840 
   1841 	return uri;
   1842 }
   1843 
   1844 static void textview_shift_uris_after(TextView *textview, GSList *start_list, gint start, gint shift)
   1845 {
   1846 	GSList *cur;
   1847 	if (!start_list)
   1848 		start_list = textview->uri_list;
   1849 
   1850 	for (cur = start_list; cur; cur = cur->next) {
   1851 		ClickableText *uri = (ClickableText *)cur->data;
   1852 		if (uri->start <= start)
   1853 			continue;
   1854 		uri->start += shift;
   1855 		uri->end += shift;
   1856 	}
   1857 }
   1858 
   1859 static void textview_remove_uris_in(TextView *textview, gint start, gint end)
   1860 {
   1861 	GSList *cur;
   1862 	for (cur = textview->uri_list; cur; ) {
   1863 		ClickableText *uri = (ClickableText *)cur->data;
   1864 		if (uri->start > start && uri->end < end) {
   1865 			cur = cur->next;
   1866 			textview->uri_list = g_slist_remove(textview->uri_list, uri);
   1867 			g_free(uri->uri);
   1868 			g_free(uri->filename);
   1869 			if (uri->is_quote) {
   1870 				g_free(uri->fg_color);
   1871 				g_free(uri->data);
   1872 				/* (only free data in quotes uris) */
   1873 			}
   1874 			g_free(uri);
   1875 		} else {
   1876 			cur = cur->next;
   1877 		}
   1878 
   1879 	}
   1880 }
   1881 
   1882 static void textview_toggle_quote(TextView *textview, GSList *start_list, ClickableText *uri, gboolean expand_only)
   1883 {
   1884 	GtkTextIter start, end;
   1885 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
   1886 
   1887 	if (!uri->is_quote)
   1888 		return;
   1889 
   1890 	if (uri->q_expanded && expand_only)
   1891 		return;
   1892 
   1893 	gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
   1894 	gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
   1895 	if (textview->uri_hover)
   1896 		gtk_text_buffer_remove_tag_by_name(buffer,
   1897 						   "link-hover",
   1898 						   &textview->uri_hover_start_iter,
   1899 						   &textview->uri_hover_end_iter);
   1900 	textview->uri_hover = NULL;
   1901 	gtk_text_buffer_remove_tag_by_name(buffer,
   1902 					   "qlink",
   1903 					   &start,
   1904 					   &end);
   1905 	/* when shifting URIs start and end, we have to do it per-UTF8-char
   1906 	 * so use g_utf8_strlen(). OTOH, when inserting in the text buffer,
   1907 	 * we have to pass a number of bytes, so use strlen(). disturbing. */
   1908 
   1909 	if (!uri->q_expanded) {
   1910 		gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
   1911 		gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
   1912 		textview_shift_uris_after(textview, start_list, uri->start,
   1913 			g_utf8_strlen((gchar *)uri->data, -1)-strlen(" [...]\n"));
   1914 		gtk_text_buffer_delete(buffer, &start, &end);
   1915 		gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
   1916 		gtk_text_buffer_insert_with_tags_by_name
   1917 				(buffer, &start, (gchar *)uri->data,
   1918 				 strlen((gchar *)uri->data)-1,
   1919 				 "qlink", (gchar *)uri->fg_color, NULL);
   1920 		uri->end = gtk_text_iter_get_offset(&start);
   1921 		textview_make_clickable_parts_later(textview,
   1922 					  uri->start, uri->end);
   1923 		uri->q_expanded = TRUE;
   1924 	} else {
   1925 		gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
   1926 		gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
   1927 		textview_remove_uris_in(textview, uri->start, uri->end);
   1928 		textview_shift_uris_after(textview, start_list, uri->start,
   1929 			strlen(" [...]\n")-g_utf8_strlen((gchar *)uri->data, -1));
   1930 		gtk_text_buffer_delete(buffer, &start, &end);
   1931 		gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
   1932 		gtk_text_buffer_insert_with_tags_by_name
   1933 				(buffer, &start, " [...]", -1,
   1934 				 "qlink", (gchar *)uri->fg_color, NULL);
   1935 		uri->end = gtk_text_iter_get_offset(&start);
   1936 		uri->q_expanded = FALSE;
   1937 	}
   1938 	if (textview->messageview->mainwin->cursor_count == 0) {
   1939 		textview_cursor_normal(textview);
   1940 	} else {
   1941 		textview_cursor_wait(textview);
   1942 	}
   1943 }
   1944 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
   1945 					    GdkEvent *event, GtkTextIter *iter,
   1946 					    TextView *textview)
   1947 {
   1948 	GdkEventButton *bevent;
   1949 	ClickableText *uri = NULL;
   1950 	char *tagname;
   1951 	gboolean qlink = FALSE;
   1952 
   1953 	if (!event)
   1954 		return FALSE;
   1955 
   1956 	if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
   1957 		&& event->type != GDK_MOTION_NOTIFY)
   1958 		return FALSE;
   1959 
   1960 	uri = textview_get_uri(textview, iter, tag);
   1961 	if (!uri)
   1962 		return FALSE;
   1963 
   1964 	g_object_get(G_OBJECT(tag), "name", &tagname, NULL);
   1965 
   1966 	if (!strcmp(tagname, "qlink"))
   1967 		qlink = TRUE;
   1968 
   1969 	g_free(tagname);
   1970 
   1971 	bevent = (GdkEventButton *) event;
   1972 
   1973 	/* doubleclick: open compose / add address / browser */
   1974 	if (qlink && event->type == GDK_BUTTON_PRESS && bevent->button != 1) {
   1975 		/* pass rightclick through */
   1976 		return FALSE;
   1977 	} else if ((event->type == (qlink ? GDK_2BUTTON_PRESS:GDK_BUTTON_PRESS) && bevent->button == 1) ||
   1978 		bevent->button == 2 || bevent->button == 3) {
   1979 		if (uri->filename && !g_ascii_strncasecmp(uri->filename, "cm://", 5)) {
   1980 			MimeView *mimeview =
   1981 				(textview->messageview)?
   1982 					textview->messageview->mimeview:NULL;
   1983 			if (mimeview && bevent->button == 1) {
   1984 				mimeview_handle_cmd(mimeview, uri->filename, NULL, uri->data);
   1985 			} else if (mimeview && bevent->button == 2 &&
   1986 				!g_ascii_strcasecmp(uri->filename, "cm://select_attachment")) {
   1987 				mimeview_handle_cmd(mimeview, "cm://open_attachment", NULL, uri->data);
   1988 			} else if (mimeview && bevent->button == 3 &&
   1989 				!g_ascii_strcasecmp(uri->filename, "cm://select_attachment")) {
   1990 				mimeview_handle_cmd(mimeview, "cm://menu_attachment", bevent, uri->data);
   1991 			}
   1992 			return TRUE;
   1993 		} else if (qlink && bevent->button == 1) {
   1994 			if (prefs_common.hide_quoted) {
   1995 				textview_toggle_quote(textview, NULL, uri, FALSE);
   1996 				return TRUE;
   1997 			} else
   1998 				return FALSE;
   1999 		} else if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
   2000 			if (bevent->button == 3) {
   2001 				g_object_set_data(
   2002 					G_OBJECT(textview->mail_popup_menu),
   2003 					"menu_button", uri);
   2004 				gtk_menu_popup_at_pointer(GTK_MENU(textview->mail_popup_menu), NULL);
   2005 			} else {
   2006 				PrefsAccount *account = NULL;
   2007 				FolderItem   *folder_item = NULL;
   2008 
   2009 				if (textview->messageview && textview->messageview->msginfo &&
   2010 				    textview->messageview->msginfo->folder) {
   2011 					folder_item = textview->messageview->msginfo->folder;
   2012 					if (folder_item->prefs && folder_item->prefs->enable_default_account)
   2013 						account = account_find_from_id(folder_item->prefs->default_account);
   2014 					if (!account)
   2015 						account = account_find_from_item(folder_item);
   2016 				}
   2017 				compose_new_with_folderitem(account, folder_item, uri->uri + 7);
   2018 			}
   2019 			return TRUE;
   2020 		} else if (g_ascii_strncasecmp(uri->uri, "file:", 5)) {
   2021 			if (bevent->button == 1 &&
   2022 			    textview_uri_security_check(textview, uri, FALSE) == TRUE)
   2023 					open_uri(uri->uri,
   2024 						 prefs_common_get_uri_cmd());
   2025 			else if (bevent->button == 3 && !qlink) {
   2026 				g_object_set_data(
   2027 					G_OBJECT(textview->link_popup_menu),
   2028 					"menu_button", uri);
   2029 				gtk_menu_popup_at_pointer(GTK_MENU(textview->link_popup_menu), NULL);
   2030 			}
   2031 			return TRUE;
   2032 		} else {
   2033 			if (bevent->button == 3 && !qlink) {
   2034 				g_object_set_data(
   2035 					G_OBJECT(textview->file_popup_menu),
   2036 					"menu_button", uri);
   2037 				gtk_menu_popup_at_pointer(GTK_MENU(textview->file_popup_menu), NULL);
   2038 				return TRUE;
   2039 			}
   2040 		}
   2041 	}
   2042 
   2043 	return FALSE;
   2044 }
   2045 
   2046 gchar *textview_get_visible_uri		(TextView 	*textview,
   2047 					 ClickableText 	*uri)
   2048 {
   2049 	GtkTextBuffer *buffer;
   2050 	GtkTextIter start, end;
   2051 
   2052 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
   2053 
   2054 	gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
   2055 	gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
   2056 
   2057 	return gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
   2058 }
   2059 
   2060 /*!
   2061  *\brief    Check to see if a web URL has been disguised as a different
   2062  *          URL (possible with HTML email).
   2063  *
   2064  *\param    uri The uri to check
   2065  *
   2066  *\param    textview The TextView the URL is contained in
   2067  *
   2068  *\return   gboolean TRUE if the URL is ok, or if the user chose to open
   2069  *          it anyway, otherwise FALSE
   2070  */
   2071 gboolean textview_uri_security_check(TextView *textview, ClickableText *uri, gboolean copied)
   2072 {
   2073 	gchar *visible_str;
   2074 	gboolean retval = TRUE;
   2075 
   2076 	if (is_uri_string(uri->uri) == FALSE)
   2077 		return FALSE;
   2078 
   2079 	visible_str = textview_get_visible_uri(textview, uri);
   2080 	if (visible_str == NULL)
   2081 		return TRUE;
   2082 
   2083 	g_strstrip(visible_str);
   2084 
   2085 	if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
   2086 		gchar *uri_path;
   2087 		gchar *visible_uri_path;
   2088 
   2089 		uri_path = get_uri_path(uri->uri);
   2090 		visible_uri_path = get_uri_path(visible_str);
   2091 		if (path_cmp(uri_path, visible_uri_path) != 0)
   2092 			retval = FALSE;
   2093 	}
   2094 
   2095 	if (retval == FALSE) {
   2096 		gchar *open_or_cp;
   2097 		gchar *open_or_cp_btn;
   2098 		gchar *msg;
   2099 		AlertValue aval;
   2100 
   2101 		open_or_cp = copied? _("Copy it anyway?") : _("Open it anyway?");
   2102 		open_or_cp_btn = copied? _("Co_py URL") : _("_Open URL");
   2103 
   2104 		msg = g_markup_printf_escaped("%s\n\n"
   2105 						"<b>%s</b> %s\n\n"
   2106 						"<b>%s</b> %s\n\n"
   2107 						"%s",
   2108 						_("The real URL is different from the displayed URL."),
   2109 						_("Displayed URL:"), visible_str,
   2110 						_("Real URL:"), uri->uri,
   2111 						open_or_cp);
   2112 		aval = alertpanel_full(_("Phishing attempt warning"), msg,
   2113 				       NULL, _("_Cancel"), NULL, open_or_cp_btn, NULL, NULL,
   2114 				       ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
   2115 		g_free(msg);
   2116 		if (aval == G_ALERTALTERNATE)
   2117 			retval = TRUE;
   2118 	}
   2119 	if (strlen(uri->uri) > get_uri_len(uri->uri))
   2120 		retval = FALSE;
   2121 
   2122 	g_free(visible_str);
   2123 
   2124 	return retval;
   2125 }
   2126 
   2127 static void textview_uri_list_remove_all(GSList *uri_list)
   2128 {
   2129 	GSList *cur;
   2130 
   2131 	for (cur = uri_list; cur != NULL; cur = cur->next) {
   2132 		if (cur->data) {
   2133 			g_free(((ClickableText *)cur->data)->uri);
   2134 			g_free(((ClickableText *)cur->data)->filename);
   2135 			if (((ClickableText *)cur->data)->is_quote) {
   2136 				g_free(((ClickableText *)cur->data)->fg_color);
   2137 				g_free(((ClickableText *)cur->data)->data);
   2138 				/* (only free data in quotes uris) */
   2139 			}
   2140 			g_free(cur->data);
   2141 		}
   2142 	}
   2143 
   2144 	g_slist_free(uri_list);
   2145 }
   2146 
   2147 static void open_uri_cb (GtkAction *action, TextView *textview)
   2148 {
   2149 	ClickableText *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
   2150 					   "menu_button");
   2151 	const gchar *raw_url = g_object_get_data(G_OBJECT(textview->link_popup_menu),
   2152 					   "raw_url");
   2153 
   2154 	if (uri) {
   2155 		if (textview_uri_security_check(textview, uri, FALSE) == TRUE)
   2156 			open_uri(uri->uri,
   2157 				 prefs_common_get_uri_cmd());
   2158 		g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
   2159 				  NULL);
   2160 	}
   2161 	if (raw_url) {
   2162 		open_uri(raw_url, prefs_common_get_uri_cmd());
   2163 		g_object_set_data(G_OBJECT(textview->link_popup_menu), "raw_url",
   2164 				  NULL);
   2165 	}
   2166 }
   2167 
   2168 static void copy_uri_cb	(GtkAction *action, TextView *textview)
   2169 {
   2170 	ClickableText *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
   2171 					   "menu_button");
   2172 	const gchar *raw_url =  g_object_get_data(G_OBJECT(textview->link_popup_menu),
   2173 					   "raw_url");
   2174 	if (uri) {
   2175 		if (textview_uri_security_check(textview, uri, TRUE) == TRUE) {
   2176 			gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), uri->uri, -1);
   2177 			gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), uri->uri, -1);
   2178 			g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button", NULL);
   2179 		}
   2180 	}
   2181 	if (raw_url) {
   2182 		gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), raw_url, -1);
   2183 		gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), raw_url, -1);
   2184 		g_object_set_data(G_OBJECT(textview->link_popup_menu), "raw_url", NULL);
   2185 	}
   2186 }
   2187 
   2188 static void add_uri_to_addrbook_cb (GtkAction *action, TextView *textview)
   2189 {
   2190 	gchar *fromname, *fromaddress;
   2191 	ClickableText *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
   2192 					   "menu_button");
   2193 
   2194 	if (uri == NULL)
   2195 		return;
   2196 
   2197 	/* extract url */
   2198 	fromaddress = g_strdup(uri->uri + 7);
   2199 
   2200 	fromname = procheader_get_fromname(fromaddress);
   2201 	extract_address(fromaddress);
   2202 
   2203 	addressbook_add_contact( fromname, fromaddress, NULL, NULL);
   2204 
   2205 	g_free(fromaddress);
   2206 	g_free(fromname);
   2207 }
   2208 
   2209 static void reply_to_uri_cb (GtkAction *action, TextView *textview)
   2210 {
   2211 	ClickableText *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
   2212 					   "menu_button");
   2213 	if (!textview->messageview || !uri)
   2214 		return;
   2215 
   2216 	compose_reply_to_address (textview->messageview,
   2217 				  textview->messageview->msginfo, uri->uri+7);
   2218 }
   2219 
   2220 static void mail_to_uri_cb(GtkAction *action, TextView *textview)
   2221 {
   2222 	fprintf(stderr, "TODO(otl): no-op mail_to_uri_cb called, delete me\n");
   2223 }
   2224 
   2225 static void copy_mail_to_uri_cb	(GtkAction *action, TextView *textview)
   2226 {
   2227 	ClickableText *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
   2228 					   "menu_button");
   2229 	if (uri == NULL)
   2230 		return;
   2231 
   2232 	gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), uri->uri +7, -1);
   2233 	gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), uri->uri +7, -1);
   2234 	g_object_set_data(G_OBJECT(textview->mail_popup_menu), "menu_button",
   2235 			  NULL);
   2236 }
   2237 
   2238 void textview_get_selection_offsets(TextView *textview, gint *sel_start, gint *sel_end)
   2239 {
   2240 		GtkTextView *text = GTK_TEXT_VIEW(textview->text);
   2241 		GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
   2242 		GtkTextIter start, end;
   2243 		if (gtk_text_buffer_get_selection_bounds(buffer, &start, &end)) {
   2244 			if (sel_start)
   2245 				*sel_start = gtk_text_iter_get_offset(&start);
   2246 			if (sel_end)
   2247 				*sel_end = gtk_text_iter_get_offset(&end);
   2248 		} else {
   2249 			if (sel_start)
   2250 				*sel_start = -1;
   2251 			if (sel_end)
   2252 				*sel_end = -1;
   2253 		}
   2254 }