1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2013 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 2 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, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23 
24 #include "defs.h"
25 
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gdk/gdk.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <gdk-pixbuf/gdk-pixbuf.h>
31 #include <gtk/gtkvbox.h>
32 #include <gtk/gtkscrolledwindow.h>
33 #include <gtk/gtksignal.h>
34 #include <gtk/gtkmenu.h>
35 #include <gtk/gtkmenuitem.h>
36 #include <gtk/gtkseparatormenuitem.h>
37 #include <gtk/gtkstock.h>
38 #include <stdio.h>
39 #include <ctype.h>
40 #include <string.h>
41 #include <stdlib.h>
42 
43 #include "main.h"
44 #include "summaryview.h"
45 #include "imageview.h"
46 #include "procheader.h"
47 #include "prefs_common.h"
48 #include "codeconv.h"
49 #include "statusbar.h"
50 #include "utils.h"
51 #include "gtkutils.h"
52 #include "procmime.h"
53 #include "account.h"
54 #include "html.h"
55 #include "compose.h"
56 #include "displayheader.h"
57 #include "filesel.h"
58 #include "alertpanel.h"
59 #include "menu.h"
60 #include "plugin.h"
61 
62 typedef struct _RemoteURI	RemoteURI;
63 
64 struct _RemoteURI
65 {
66 	gchar *uri;
67 
68 	gchar *filename;
69 
70 	guint start;
71 	guint end;
72 };
73 
74 static GdkColor quote_colors[3] = {
75 	{(gulong)0, (gushort)0, (gushort)0, (gushort)0},
76 	{(gulong)0, (gushort)0, (gushort)0, (gushort)0},
77 	{(gulong)0, (gushort)0, (gushort)0, (gushort)0}
78 };
79 
80 static GdkColor uri_color = {
81 	(gulong)0,
82 	(gushort)0,
83 	(gushort)0,
84 	(gushort)0
85 };
86 
87 static GdkColor emphasis_color = {
88 	(gulong)0,
89 	(gushort)0,
90 	(gushort)0,
91 	(gushort)0xcfff
92 };
93 
94 static GdkColor error_color = {
95 	(gulong)0,
96 	(gushort)0xefff,
97 	(gushort)0,
98 	(gushort)0
99 };
100 
101 #if USE_GPGME
102 static GdkColor good_sig_color = {
103 	(gulong)0,
104 	(gushort)0,
105 	(gushort)0xbfff,
106 	(gushort)0
107 };
108 
109 static GdkColor untrusted_sig_color = {
110 	(gulong)0,
111 	(gushort)0xefff,
112 	(gushort)0,
113 	(gushort)0
114 };
115 
116 static GdkColor nocheck_sig_color = {
117 	(gulong)0,
118 	(gushort)0,
119 	(gushort)0,
120 	(gushort)0xcfff
121 };
122 
123 static GdkColor bad_sig_color = {
124 	(gulong)0,
125 	(gushort)0xefff,
126 	(gushort)0,
127 	(gushort)0
128 };
129 #endif
130 
131 #define STATUSBAR_PUSH(textview, str)					    \
132 {									    \
133 	gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
134 			   textview->messageview->statusbar_cid, str);	    \
135 }
136 
137 #define STATUSBAR_POP(textview)						   \
138 {									   \
139 	gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
140 			  textview->messageview->statusbar_cid);	   \
141 }
142 
143 static GdkCursor *hand_cursor = NULL;
144 static GdkCursor *regular_cursor = NULL;
145 
146 
147 static void textview_part_menu_create	(TextView	*textview);
148 
149 static void textview_add_part		(TextView	*textview,
150 					 MimeInfo	*mimeinfo,
151 					 FILE		*fp);
152 #if USE_GPGME
153 static void textview_add_sig_part	(TextView	*textview,
154 					 MimeInfo	*mimeinfo);
155 #endif
156 static void textview_add_parts		(TextView	*textview,
157 					 MimeInfo	*mimeinfo,
158 					 FILE		*fp);
159 static void textview_write_body		(TextView	*textview,
160 					 MimeInfo	*mimeinfo,
161 					 FILE		*fp,
162 					 const gchar	*charset);
163 static void textview_show_html		(TextView	*textview,
164 					 FILE		*fp,
165 					 CodeConverter	*conv);
166 
167 static void textview_write_line		(TextView	*textview,
168 					 const gchar	*str,
169 					 CodeConverter	*conv);
170 static void textview_write_link		(TextView	*textview,
171 					 const gchar	*str,
172 					 const gchar	*uri,
173 					 CodeConverter	*conv);
174 
175 static GPtrArray *textview_scan_header	(TextView	*textview,
176 					 FILE		*fp,
177 					 const gchar	*encoding);
178 static void textview_show_header	(TextView	*textview,
179 					 GPtrArray	*headers);
180 
181 static void textview_insert_border	(TextView	*textview,
182 					 GtkTextIter	*iter,
183 					 gint		 padding);
184 static void textview_insert_pad		(TextView	*textview,
185 					 GtkTextIter	*iter,
186 					 gint		 padding);
187 
188 static gboolean textview_key_pressed		(GtkWidget	*widget,
189 						 GdkEventKey	*event,
190 						 TextView	*textview);
191 static gboolean textview_event_after		(GtkWidget	*widget,
192 						 GdkEvent	*event,
193 						 TextView	*textview);
194 static gboolean textview_motion_notify		(GtkWidget	*widget,
195 						 GdkEventMotion	*event,
196 						 TextView	*textview);
197 static gboolean textview_leave_notify		(GtkWidget	  *widget,
198 						 GdkEventCrossing *event,
199 						 TextView	  *textview);
200 static gboolean textview_visibility_notify	(GtkWidget	*widget,
201 						 GdkEventVisibility *event,
202 						 TextView	*textview);
203 
204 static void textview_populate_popup		(GtkWidget	*widget,
205 						 GtkMenu	*menu,
206 						 TextView	*textview);
207 static void textview_popup_menu_activate_open_uri_cb
208 						(GtkMenuItem	*menuitem,
209 						 gpointer	 data);
210 static void textview_popup_menu_activate_reply_cb
211 						(GtkMenuItem	*menuitem,
212 						 gpointer	 data);
213 static void textview_popup_menu_activate_add_address_cb
214 						(GtkMenuItem	*menuitem,
215 						 gpointer	 data);
216 static void textview_popup_menu_activate_copy_cb(GtkMenuItem	*menuitem,
217 						 gpointer	 data);
218 static void textview_popup_menu_activate_image_cb
219 						(GtkMenuItem	*menuitem,
220 						 gpointer	 data);
221 
222 static void textview_adj_value_changed		(GtkAdjustment	*adj,
223 						 gpointer	 data);
224 
225 static void textview_smooth_scroll_do		(TextView	*textview,
226 						 gfloat		 old_value,
227 						 gfloat		 last_value,
228 						 gint		 step);
229 static void textview_smooth_scroll_one_line	(TextView	*textview,
230 						 gboolean	 up);
231 static gboolean textview_smooth_scroll_page	(TextView	*textview,
232 						 gboolean	 up);
233 
234 static gboolean textview_get_link_tag_bounds	(TextView	*textview,
235 						 GtkTextIter	*iter,
236 						 GtkTextIter	*start,
237 						 GtkTextIter	*end);
238 static RemoteURI *textview_get_uri		(TextView	*textview,
239 						 GtkTextIter	*start,
240 						 GtkTextIter	*end);
241 static void textview_show_uri			(TextView	*textview,
242 						 GtkTextIter	*start,
243 						 GtkTextIter	*end);
244 static void textview_set_cursor			(TextView	*textview,
245 						 GtkTextView	*text,
246 						 gint		 x,
247 						 gint		 y);
248 
249 static gboolean textview_uri_security_check	(TextView	*textview,
250 						 RemoteURI	*uri);
251 static void textview_uri_list_remove_all	(GSList		*uri_list);
252 static void textview_uri_list_update_offsets	(TextView	*textview,
253 						 gint		 start,
254 						 gint		 add);
255 
256 
textview_create(void)257 TextView *textview_create(void)
258 {
259 	TextView *textview;
260 	GtkWidget *vbox;
261 	GtkWidget *scrolledwin;
262 	GtkWidget *text;
263 	GtkTextBuffer *buffer;
264 	GtkClipboard *clipboard;
265 
266 	debug_print(_("Creating text view...\n"));
267 	textview = g_new0(TextView, 1);
268 
269 	scrolledwin = gtk_scrolled_window_new(NULL, NULL);
270 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
271 				       GTK_POLICY_AUTOMATIC,
272 				       GTK_POLICY_AUTOMATIC);
273 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
274 					    GTK_SHADOW_ETCHED_IN);
275 	gtk_widget_set_size_request
276 		(scrolledwin, prefs_common.mainview_width, -1);
277 
278 	text = gtk_text_view_new();
279 	gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
280 	gtk_widget_show(text);
281 	gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
282 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
283 	gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
284 	gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
285 
286 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
287 	clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
288 	gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
289 
290 	gtk_widget_ref(scrolledwin);
291 
292 	gtk_container_add(GTK_CONTAINER(scrolledwin), text);
293 
294 	g_signal_connect(G_OBJECT(text), "key-press-event",
295 			 G_CALLBACK(textview_key_pressed), textview);
296 	g_signal_connect(G_OBJECT(text), "event-after",
297 			 G_CALLBACK(textview_event_after), textview);
298 	g_signal_connect(G_OBJECT(text), "motion-notify-event",
299 			 G_CALLBACK(textview_motion_notify), textview);
300 	g_signal_connect(G_OBJECT(text), "leave-notify-event",
301 			 G_CALLBACK(textview_leave_notify), textview);
302 	g_signal_connect(G_OBJECT(text), "visibility-notify-event",
303 			 G_CALLBACK(textview_visibility_notify), textview);
304 	g_signal_connect(G_OBJECT(text), "populate-popup",
305 			 G_CALLBACK(textview_populate_popup), textview);
306 
307 	g_signal_connect(G_OBJECT(GTK_TEXT_VIEW(text)->vadjustment),
308 			 "value-changed",
309 			 G_CALLBACK(textview_adj_value_changed), textview);
310 
311 	gtk_widget_show(scrolledwin);
312 
313 	vbox = gtk_vbox_new(FALSE, 0);
314 	gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
315 
316 	gtk_widget_show(vbox);
317 
318 	textview->vbox             = vbox;
319 	textview->scrolledwin      = scrolledwin;
320 	textview->text             = text;
321 	textview->uri_list         = NULL;
322 	textview->body_pos         = 0;
323 	textview->show_all_headers = FALSE;
324 
325 	textview_part_menu_create(textview);
326 
327 	return textview;
328 }
329 
textview_create_tags(TextView * textview)330 static void textview_create_tags(TextView *textview)
331 {
332 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
333 	GtkTextBuffer *buffer;
334 	static PangoFontDescription *font_desc;
335 	GtkTextIter iter;
336 	GtkTextMark *mark;
337 
338 	if (!font_desc)
339 		font_desc = gtkut_get_default_font_desc();
340 
341 	buffer = gtk_text_view_get_buffer(text);
342 
343 	gtk_text_buffer_get_end_iter(buffer, &iter);
344 	mark = gtk_text_buffer_create_mark(buffer, "attach-file-pos",
345 					   &iter, TRUE);
346 
347 	gtk_text_buffer_create_tag(buffer, "header",
348 				   "pixels-above-lines", 1,
349 				   "pixels-above-lines-set", TRUE,
350 				   "pixels-below-lines", 0,
351 				   "pixels-below-lines-set", TRUE,
352 				   "font-desc", font_desc,
353 				   NULL);
354 	gtk_text_buffer_create_tag(buffer, "header_title",
355 				   "weight", PANGO_WEIGHT_BOLD,
356 				   NULL);
357 
358 	gtk_text_buffer_create_tag(buffer, "mimepart",
359 				   "pixels-above-lines", 1,
360 				   "pixels-above-lines-set", TRUE,
361 				   "pixels-below-lines", 1,
362 				   "pixels-below-lines-set", TRUE,
363 				   "font-desc", font_desc,
364 				   NULL);
365 
366 	textview->quote0_tag =
367 		gtk_text_buffer_create_tag(buffer, "quote0",
368 					   "foreground-gdk", &quote_colors[0],
369 					   NULL);
370 	textview->quote1_tag =
371 		gtk_text_buffer_create_tag(buffer, "quote1",
372 					   "foreground-gdk", &quote_colors[1],
373 					   NULL);
374 	textview->quote2_tag =
375 		gtk_text_buffer_create_tag(buffer, "quote2",
376 					   "foreground-gdk", &quote_colors[2],
377 					   NULL);
378 	textview->link_tag =
379 		gtk_text_buffer_create_tag(buffer, "link",
380 					   "foreground-gdk", &uri_color,
381 					   NULL);
382 	textview->hover_link_tag =
383 		gtk_text_buffer_create_tag(buffer, "hover-link",
384 					   "foreground-gdk", &uri_color,
385 					   "underline", PANGO_UNDERLINE_SINGLE,
386 					   NULL);
387 
388 	gtk_text_buffer_create_tag(buffer, "emphasis",
389 				   "foreground-gdk", &emphasis_color,
390 				   NULL);
391 	gtk_text_buffer_create_tag(buffer, "error",
392 				   "foreground-gdk", &error_color,
393 				   NULL);
394 #if USE_GPGME
395 	gtk_text_buffer_create_tag(buffer, "good-signature",
396 				   "foreground-gdk", &good_sig_color,
397 				   NULL);
398 	gtk_text_buffer_create_tag(buffer, "untrusted-signature",
399 				   "foreground-gdk", &untrusted_sig_color,
400 				   NULL);
401 	gtk_text_buffer_create_tag(buffer, "bad-signature",
402 				   "foreground-gdk", &bad_sig_color,
403 				   NULL);
404 	gtk_text_buffer_create_tag(buffer, "nocheck-signature",
405 				   "foreground-gdk", &nocheck_sig_color,
406 				   NULL);
407 #endif /* USE_GPGME */
408 }
409 
textview_init(TextView * textview)410 void textview_init(TextView *textview)
411 {
412 	if (!hand_cursor)
413 		hand_cursor = gdk_cursor_new(GDK_HAND2);
414 	if (!regular_cursor)
415 		regular_cursor = gdk_cursor_new(GDK_XTERM);
416 
417 	textview_create_tags(textview);
418 	textview_reflect_prefs(textview);
419 	textview_set_all_headers(textview, FALSE);
420 	textview_set_font(textview, NULL);
421 }
422 
textview_update_message_colors(void)423 static void textview_update_message_colors(void)
424 {
425 	GdkColor black = {0, 0, 0, 0};
426 
427 	if (prefs_common.enable_color) {
428 		/* grab the quote colors, converting from an int to a
429 		   GdkColor */
430 		gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
431 					       &quote_colors[0]);
432 		gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
433 					       &quote_colors[1]);
434 		gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
435 					       &quote_colors[2]);
436 		gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
437 					       &uri_color);
438 	} else {
439 		quote_colors[0] = quote_colors[1] = quote_colors[2] =
440 			uri_color = emphasis_color = black;
441 	}
442 }
443 
textview_update_tags(TextView * textview)444 static void textview_update_tags(TextView *textview)
445 {
446 	g_object_set(textview->quote0_tag, "foreground-gdk", &quote_colors[0],
447 		     NULL);
448 	g_object_set(textview->quote1_tag, "foreground-gdk", &quote_colors[1],
449 		     NULL);
450 	g_object_set(textview->quote2_tag, "foreground-gdk", &quote_colors[2],
451 		     NULL);
452 	g_object_set(textview->link_tag, "foreground-gdk", &uri_color, NULL);
453 	g_object_set(textview->hover_link_tag, "foreground-gdk", &uri_color,
454 		     NULL);
455 }
456 
textview_reflect_prefs(TextView * textview)457 void textview_reflect_prefs(TextView *textview)
458 {
459 	textview_update_message_colors();
460 	textview_update_tags(textview);
461 	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview->text),
462 					 prefs_common.textview_cursor_visible);
463 }
464 
textview_get_src_encoding(TextView * textview,MimeInfo * mimeinfo)465 static const gchar *textview_get_src_encoding(TextView *textview,
466 					      MimeInfo *mimeinfo)
467 {
468 	const gchar *charset;
469 
470 	if (textview->messageview->forced_charset)
471 		charset = textview->messageview->forced_charset;
472 	else if (!textview->messageview->new_window &&
473 		 prefs_common.force_charset)
474 		charset = prefs_common.force_charset;
475 	else if (mimeinfo->charset)
476 		charset = mimeinfo->charset;
477 	else if (prefs_common.default_encoding)
478 		charset = prefs_common.default_encoding;
479 	else
480 		charset = NULL;
481 
482 	return charset;
483 }
484 
textview_show_message(TextView * textview,MimeInfo * mimeinfo,const gchar * file)485 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
486 			   const gchar *file)
487 {
488 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
489 	GtkTextBuffer *buffer;
490 	GtkTextMark *mark;
491 	GtkTextIter iter;
492 	FILE *fp;
493 	const gchar *charset;
494 	GPtrArray *headers = NULL;
495 
496 	buffer = gtk_text_view_get_buffer(text);
497 
498 	if ((fp = g_fopen(file, "rb")) == NULL) {
499 		FILE_OP_ERROR(file, "fopen");
500 		return;
501 	}
502 
503 	debug_print("textview_show_message: displaying: %s\n", file);
504 
505 	charset = textview_get_src_encoding(textview, mimeinfo);
506 	textview_set_font(textview, charset);
507 	textview_clear(textview);
508 
509 	if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) perror("fseek");
510 	headers = textview_scan_header(textview, fp, charset);
511 	if (headers) {
512 		textview_show_header(textview, headers);
513 		procheader_header_array_destroy(headers);
514 
515 		gtk_text_buffer_get_end_iter(buffer, &iter);
516 		textview->body_pos = gtk_text_iter_get_offset(&iter);
517 	} else {
518 		gtk_text_buffer_get_end_iter(buffer, &iter);
519 		mark = gtk_text_buffer_get_mark(buffer, "attach-file-pos");
520 		gtk_text_buffer_move_mark(buffer, mark, &iter);
521 		g_object_set_data(G_OBJECT(mark), "attach-file-count", GINT_TO_POINTER(0));
522 	}
523 
524 #if USE_GPGME
525 	if (textview->messageview->msginfo->encinfo &&
526 	    textview->messageview->msginfo->encinfo->decryption_failed) {
527 		gtk_text_buffer_get_end_iter(buffer, &iter);
528 		gtk_text_buffer_insert(buffer, &iter, "\n", 1);
529 		gtk_text_buffer_insert_with_tags_by_name
530 			(buffer, &iter, _("This message is encrypted, but its decryption failed.\n"),
531 			 -1, "error", "mimepart", NULL);
532 	}
533 #endif
534 
535 	textview_add_parts(textview, mimeinfo, fp);
536 
537 #if USE_GPGME
538 	if (textview->messageview->msginfo->encinfo &&
539 	    textview->messageview->msginfo->encinfo->sigstatus)
540 		textview_add_sig_part(textview, NULL);
541 #endif
542 
543 	fclose(fp);
544 
545 	textview_set_position(textview, 0);
546 	mark = gtk_text_buffer_get_insert(buffer);
547 	gtk_text_view_scroll_mark_onscreen(text, mark);
548 }
549 
textview_show_part(TextView * textview,MimeInfo * mimeinfo,FILE * fp)550 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
551 {
552 	gchar buf[BUFFSIZE];
553 	const gchar *boundary = NULL;
554 	gint boundary_len = 0;
555 	const gchar *charset;
556 	GPtrArray *headers = NULL;
557 	gboolean is_rfc822_part = FALSE;
558 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
559 	GtkTextBuffer *buffer;
560 	GtkTextIter iter;
561 	GtkTextMark *mark;
562 
563 	g_return_if_fail(mimeinfo != NULL);
564 	g_return_if_fail(fp != NULL);
565 
566 	if (mimeinfo->mime_type == MIME_MULTIPART) {
567 		textview_clear(textview);
568 		textview_add_parts(textview, mimeinfo, fp);
569 		return;
570 	}
571 
572 	if (mimeinfo->parent && mimeinfo->parent->boundary) {
573 		boundary = mimeinfo->parent->boundary;
574 		boundary_len = strlen(boundary);
575 	}
576 
577 	charset = textview_get_src_encoding(textview, mimeinfo);
578 
579 	if (!boundary && mimeinfo->mime_type == MIME_TEXT) {
580 		if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0)
581 			perror("fseek");
582 		headers = textview_scan_header(textview, fp, charset);
583 	} else {
584 		if (mimeinfo->mime_type == MIME_TEXT && mimeinfo->parent) {
585 			glong fpos;
586 			MimeInfo *parent = mimeinfo->parent;
587 
588 			while (parent->parent) {
589 				if (parent->main &&
590 				    parent->main->mime_type ==
591 					MIME_MESSAGE_RFC822)
592 					break;
593 				parent = parent->parent;
594 			}
595 
596 			if ((fpos = ftell(fp)) < 0)
597 				perror("ftell");
598 			else if (fseek(fp, parent->fpos, SEEK_SET) < 0)
599 				perror("fseek");
600 			else {
601 				headers = textview_scan_header
602 					(textview, fp, charset);
603 				if (fseek(fp, fpos, SEEK_SET) < 0)
604 					perror("fseek");
605 			}
606 		}
607 		/* skip MIME part headers */
608 		while (fgets(buf, sizeof(buf), fp) != NULL)
609 			if (buf[0] == '\r' || buf[0] == '\n') break;
610 	}
611 
612 	/* display attached RFC822 single text message */
613 	if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
614 		if (headers) procheader_header_array_destroy(headers);
615 		if (!mimeinfo->sub) {
616 			textview_clear(textview);
617 			return;
618 		}
619 		headers = textview_scan_header(textview, fp, charset);
620 		mimeinfo = mimeinfo->sub;
621 		is_rfc822_part = TRUE;
622 	}
623 
624 	textview_set_font(textview, charset);
625 	textview_clear(textview);
626 
627 	buffer = gtk_text_view_get_buffer(text);
628 
629 	if (headers) {
630 		textview_show_header(textview, headers);
631 		procheader_header_array_destroy(headers);
632 
633 		gtk_text_buffer_get_end_iter(buffer, &iter);
634 		textview->body_pos = gtk_text_iter_get_offset(&iter);
635 		if (!mimeinfo->main)
636 			gtk_text_buffer_insert(buffer, &iter, "\n", 1);
637 	} else {
638 		gtk_text_buffer_get_end_iter(buffer, &iter);
639 		mark = gtk_text_buffer_get_mark(buffer, "attach-file-pos");
640 		gtk_text_buffer_move_mark(buffer, mark, &iter);
641 		g_object_set_data(G_OBJECT(mark), "attach-file-count", GINT_TO_POINTER(0));
642 	}
643 
644 	if (mimeinfo->mime_type == MIME_MULTIPART || is_rfc822_part)
645 		textview_add_parts(textview, mimeinfo, fp);
646 	else
647 		textview_write_body(textview, mimeinfo, fp, charset);
648 
649 	textview_set_position(textview, 0);
650 	mark = gtk_text_buffer_get_insert(buffer);
651 	gtk_text_view_scroll_mark_onscreen(text, mark);
652 }
653 
654 enum {
655 	PART_MENU_NONE,
656 	PART_MENU_OPEN,
657 	PART_MENU_OPEN_WITH,
658 	PART_MENU_SAVE_AS,
659 	PART_MENU_PRINT,
660 	PART_MENU_COPY_FILENAME
661 };
662 
part_menu_button_position(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer user_data)663 static void part_menu_button_position(GtkMenu *menu, gint *x, gint *y,
664 				      gboolean *push_in,
665 				      gpointer user_data)
666 {
667 	GtkWidget *widget;
668 	GtkRequisition requisition;
669 	gint button_xpos, button_ypos;
670 	gint xpos, ypos;
671 	gint width, height;
672 	gint scr_width, scr_height;
673 
674 	g_return_if_fail(x != NULL && y != NULL);
675 
676 	widget = GTK_WIDGET(user_data);
677 	gtk_widget_get_child_requisition(GTK_WIDGET(menu), &requisition);
678 	width = requisition.width;
679 	height = requisition.height;
680 	gdk_window_get_origin(widget->window, &button_xpos, &button_ypos);
681 
682 	xpos = button_xpos;
683 	ypos = button_ypos + widget->requisition.height;
684 
685 	scr_width = gdk_screen_width();
686 	scr_height = gdk_screen_height();
687 
688 	if (xpos + width > scr_width)
689 		xpos -= (xpos + width) - scr_width;
690 	if (ypos + height > scr_height)
691 		ypos -= widget->requisition.height + height;
692 	if (xpos < 0)
693 		xpos = 0;
694 	if (ypos < 0)
695 		ypos = 0;
696 
697 	*x = xpos;
698 	*y = ypos;
699 }
700 
textview_part_widget_entered(GtkWidget * widget,GdkEventCrossing * event,gpointer data)701 static gboolean textview_part_widget_entered(GtkWidget *widget,
702 					     GdkEventCrossing *event,
703 					     gpointer data)
704 {
705 	gtk_widget_set_state(widget, GTK_STATE_PRELIGHT);
706 	return FALSE;
707 }
708 
textview_part_widget_left(GtkWidget * widget,GdkEventCrossing * event,gpointer data)709 static gboolean textview_part_widget_left(GtkWidget *widget,
710 					  GdkEventCrossing *event,
711 					  gpointer data)
712 {
713 	gtk_widget_set_state(widget, GTK_STATE_NORMAL);
714 	return FALSE;
715 }
716 
textview_part_widget_button_pressed(GtkWidget * widget,GdkEventButton * event,gpointer data)717 static gboolean textview_part_widget_button_pressed(GtkWidget *widget,
718 						    GdkEventButton *event,
719 						    gpointer data)
720 {
721 	TextView *textview = (TextView *)data;
722 	GtkWidget *menu;
723 	MimeInfo *mimeinfo;
724 	GList *cur;
725 
726 	if (!event)
727 		return FALSE;
728 
729 	menu = textview->popup_menu;
730 	mimeinfo = g_object_get_data(G_OBJECT(widget), "mimeinfo");
731 
732 	for (cur = GTK_MENU_SHELL(menu)->children; cur != NULL; cur = cur->next) {
733 		GtkWidget *menuitem = GTK_WIDGET(cur->data);
734 		gint type = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID));
735 		if (type == PART_MENU_PRINT) {
736 			if (mimeinfo->mime_type == MIME_TEXT ||
737 			    mimeinfo->mime_type == MIME_TEXT_HTML ||
738 			    mimeinfo->mime_type == MIME_MESSAGE_RFC822)
739 				gtk_widget_set_sensitive(menuitem, TRUE);
740 			else
741 				gtk_widget_set_sensitive(menuitem, FALSE);
742 		} else if (type == PART_MENU_COPY_FILENAME) {
743 			if (mimeinfo->filename || mimeinfo->name)
744 				gtk_widget_set_sensitive(menuitem, TRUE);
745 			else
746 				gtk_widget_set_sensitive(menuitem, FALSE);
747 		}
748 	}
749 
750 	g_object_set_data(G_OBJECT(menu), "part-widget", widget);
751 	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, part_menu_button_position, widget, event->button, event->time);
752 
753 	return TRUE;
754 }
755 
textview_part_widget_exposed(GtkWidget * widget,GdkEventExpose * event,gpointer data)756 static gboolean textview_part_widget_exposed(GtkWidget *widget,
757 					     GdkEventExpose *event,
758 					     gpointer data)
759 {
760 	GdkDrawable *drawable;
761 	GdkGC *gc;
762 	gint w, h;
763 
764 	w = widget->allocation.width - 1;
765 	h = widget->allocation.height - 1;
766 	if (w <= 0 || h <= 0)
767 		return FALSE;
768 
769 	drawable = GDK_DRAWABLE(widget->window);
770 	gc = widget->style->dark_gc[GTK_WIDGET_STATE(widget)];
771 	gdk_gc_set_clip_rectangle(gc, &event->area);
772 	gdk_gc_set_line_attributes(gc, 1, GDK_LINE_SOLID, GDK_CAP_NOT_LAST,
773 				   GDK_JOIN_MITER);
774 	gdk_draw_line(drawable, gc, 1, 0, w, 0);
775 	gdk_draw_line(drawable, gc, w, 1, w, h);
776 	gdk_draw_line(drawable, gc, w, h, 1, h);
777 	gdk_draw_line(drawable, gc, 0, h, 0, 1);
778 
779 	return FALSE;
780 }
781 
textview_part_menu_activated(GtkWidget * widget,gpointer data)782 static void textview_part_menu_activated(GtkWidget *widget, gpointer data)
783 {
784 	TextView *textview = (TextView *)data;
785 	GtkWidget *menu;
786 	GtkWidget *part_widget;
787 	gint type;
788 	MimeInfo *mimeinfo;
789 	MimeView *mimeview = textview->messageview->mimeview;
790 	const gchar *filename;
791 	GtkClipboard *clipboard;
792 
793 	menu = gtk_widget_get_parent(widget);
794 	part_widget = g_object_get_data(G_OBJECT(menu), "part-widget");
795 	mimeinfo = g_object_get_data(G_OBJECT(part_widget), "mimeinfo");
796 	if (!mimeinfo)
797 		return;
798 	type = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), MENU_VAL_ID));
799 
800 	switch (type) {
801 	case PART_MENU_OPEN:
802 		mimeview_launch_part(mimeview, mimeinfo);
803 		break;
804 	case PART_MENU_OPEN_WITH:
805 		mimeview_open_part_with(mimeview, mimeinfo);
806 		break;
807 	case PART_MENU_SAVE_AS:
808 		mimeview_save_part_as(mimeview, mimeinfo);
809 		break;
810 	case PART_MENU_PRINT:
811 		mimeview_print_part(mimeview, mimeinfo);
812 		break;
813 	case PART_MENU_COPY_FILENAME:
814 		filename = mimeinfo->filename ? mimeinfo->filename :
815 			   mimeinfo->name ? mimeinfo->name : NULL;
816 		if (filename) {
817 			clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
818 			gtk_clipboard_set_text(clipboard, filename, -1);
819 			clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
820 			gtk_clipboard_set_text(clipboard, filename, -1);
821 		}
822 		break;
823 	default:
824 		break;
825 	}
826 }
827 
textview_part_menu_closed(GtkMenuShell * menu_shell,gpointer data)828 static void textview_part_menu_closed(GtkMenuShell *menu_shell, gpointer data)
829 {
830 	g_object_set_data(G_OBJECT(menu_shell), "part-widget", NULL);
831 }
832 
textview_part_menu_create(TextView * textview)833 static void textview_part_menu_create(TextView *textview)
834 {
835 	GtkWidget *menu;
836 	GtkWidget *menuitem;
837 
838 	if (textview->popup_menu)
839 		return;
840 
841 	menu = gtk_menu_new();
842 	textview->popup_menu = menu;
843 
844 	MENUITEM_ADD_FROM_STOCK(menu, menuitem, GTK_STOCK_OPEN, PART_MENU_OPEN);
845 	g_signal_connect(G_OBJECT(menuitem), "activate",
846 			 G_CALLBACK(textview_part_menu_activated), textview);
847 	MENUITEM_ADD_WITH_MNEMONIC(menu, menuitem, _("Open _with..."), PART_MENU_OPEN_WITH);
848 	g_signal_connect(G_OBJECT(menuitem), "activate",
849 			 G_CALLBACK(textview_part_menu_activated), textview);
850 	MENUITEM_ADD_WITH_MNEMONIC(menu, menuitem, _("_Save as..."), PART_MENU_SAVE_AS);
851 	g_signal_connect(G_OBJECT(menuitem), "activate",
852 			 G_CALLBACK(textview_part_menu_activated), textview);
853 	MENUITEM_ADD_FROM_STOCK(menu, menuitem, GTK_STOCK_PRINT, PART_MENU_PRINT);
854 	g_signal_connect(G_OBJECT(menuitem), "activate",
855 			 G_CALLBACK(textview_part_menu_activated), textview);
856 
857 	MENUITEM_ADD(menu, menuitem, NULL, 0);
858 
859 	MENUITEM_ADD_WITH_MNEMONIC(menu, menuitem, _("_Copy file name"), PART_MENU_COPY_FILENAME);
860 	g_signal_connect(G_OBJECT(menuitem), "activate",
861 			 G_CALLBACK(textview_part_menu_activated), textview);
862 
863 	g_signal_connect(G_OBJECT(menu), "selection_done",
864 			 G_CALLBACK(textview_part_menu_closed), textview);
865 
866 	gtk_widget_show_all(menu);
867 }
868 
textview_add_part_widget(TextView * textview,GtkTextIter * iter,MimeInfo * mimeinfo,const gchar * str)869 static void textview_add_part_widget(TextView *textview, GtkTextIter *iter,
870 				     MimeInfo *mimeinfo, const gchar *str)
871 {
872 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
873 	GtkTextBuffer *buffer;
874 	GtkTextChildAnchor *anchor;
875 	GtkWidget *hbox;
876 	GtkWidget *ebox;
877 	GtkWidget *label;
878 	GtkWidget *arrow;
879 	GtkStyle *style;
880 	GdkColor bg = {0, 0xe000, 0xe500, 0xffff};
881 	GdkColor fg = {0, 0x8000, 0x9800, 0xffff};
882 	GdkColor bg2 = {0, 0xc000, 0xc800, 0xffff};
883 	GdkColor fg2 = {0, 0x6000, 0x8000, 0xffff};
884 
885 	buffer = gtk_text_view_get_buffer(text);
886 	anchor = gtk_text_buffer_create_child_anchor(buffer, iter);
887 	ebox = gtk_event_box_new();
888 	hbox = gtk_hbox_new(FALSE, 4);
889 	gtk_container_set_border_width(GTK_CONTAINER(hbox), 3);
890 	label = gtk_label_new(str);
891 	gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
892 	arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
893 	gtk_box_pack_start(GTK_BOX(hbox), arrow, FALSE, FALSE, 0);
894 	gtk_container_add(GTK_CONTAINER(ebox), hbox);
895 	gtk_widget_show_all(ebox);
896 	g_signal_connect(G_OBJECT(ebox), "button_press_event",
897 			 G_CALLBACK(textview_part_widget_button_pressed),
898 			 textview);
899 	g_signal_connect(G_OBJECT(ebox), "enter_notify_event",
900 			 G_CALLBACK(textview_part_widget_entered), textview);
901 	g_signal_connect(G_OBJECT(ebox), "leave_notify_event",
902 			 G_CALLBACK(textview_part_widget_left), textview);
903 	g_signal_connect_after(G_OBJECT(ebox), "expose_event",
904 			 G_CALLBACK(textview_part_widget_exposed), textview);
905 
906 	style = gtk_widget_get_style(label);
907 	bg = style->bg[GTK_STATE_NORMAL];
908 	fg = style->fg[GTK_STATE_NORMAL];
909 	bg2 = style->bg[GTK_STATE_PRELIGHT];
910 	fg2 = style->fg[GTK_STATE_PRELIGHT];
911 
912 	gtk_widget_modify_bg(ebox, GTK_STATE_NORMAL, &bg);
913 	gtk_widget_modify_fg(ebox, GTK_STATE_NORMAL, &fg);
914 	gtk_widget_modify_bg(ebox, GTK_STATE_PRELIGHT, &bg2);
915 	gtk_widget_modify_fg(ebox, GTK_STATE_PRELIGHT, &fg2);
916 
917 	g_object_set_data(G_OBJECT(ebox), "mimeinfo", mimeinfo);
918 
919 	gtk_text_view_add_child_at_anchor(text, ebox, anchor);
920 	gtk_text_buffer_insert(buffer, iter, "\n", 1);
921 }
922 
textview_add_part(TextView * textview,MimeInfo * mimeinfo,FILE * fp)923 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
924 {
925 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
926 	GtkTextBuffer *buffer;
927 	GtkTextIter iter;
928 	gchar buf[BUFFSIZE];
929 	const gchar *boundary = NULL;
930 	gint boundary_len = 0;
931 	const gchar *charset;
932 	GPtrArray *headers = NULL;
933 	GtkTextMark *mark;
934 
935 	g_return_if_fail(mimeinfo != NULL);
936 	g_return_if_fail(fp != NULL);
937 
938 	buffer = gtk_text_view_get_buffer(text);
939 	gtk_text_buffer_get_end_iter(buffer, &iter);
940 
941 	if (mimeinfo->mime_type == MIME_MULTIPART) return;
942 
943 	if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) {
944 		perror("fseek");
945 		return;
946 	}
947 
948 	if (mimeinfo->parent && mimeinfo->parent->boundary) {
949 		boundary = mimeinfo->parent->boundary;
950 		boundary_len = strlen(boundary);
951 	}
952 
953 	while (fgets(buf, sizeof(buf), fp) != NULL)
954 		if (buf[0] == '\r' || buf[0] == '\n') break;
955 
956 	charset = textview_get_src_encoding(textview, mimeinfo);
957 
958 	if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
959 		g_snprintf(buf, sizeof(buf), "%s (%s)",
960 			   mimeinfo->content_type,
961 			   to_human_readable(mimeinfo->content_size));
962 		debug_print("textview_add_part: adding: %s\n", buf);
963 		gtk_text_buffer_insert(buffer, &iter, "\n", 1);
964 		textview_add_part_widget(textview, &iter, mimeinfo, buf);
965 		gtk_text_buffer_get_end_iter(buffer, &iter);
966 		headers = textview_scan_header(textview, fp, charset);
967 		if (headers) {
968 			textview_show_header(textview, headers);
969 			procheader_header_array_destroy(headers);
970 		} else {
971 			mark = gtk_text_buffer_get_mark(buffer, "attach-file-pos");
972 			gtk_text_buffer_move_mark(buffer, mark, &iter);
973 			g_object_set_data(G_OBJECT(mark), "attach-file-count", GINT_TO_POINTER(0));
974 		}
975 		return;
976 	}
977 
978 #if USE_GPGME
979 	if (mimeinfo->parent && mimeinfo->sigstatus) {
980 		textview_add_sig_part(textview, mimeinfo);
981 		return;
982 	}
983 #endif
984 
985 	if (mimeinfo->filename || mimeinfo->name)
986 		g_snprintf(buf, sizeof(buf), "%s  %s (%s)",
987 			   mimeinfo->filename ? mimeinfo->filename :
988 			   mimeinfo->name,
989 			   mimeinfo->content_type,
990 			   to_human_readable(mimeinfo->content_size));
991 	else
992 		g_snprintf(buf, sizeof(buf), "%s (%s)",
993 			   mimeinfo->content_type,
994 			   to_human_readable(mimeinfo->content_size));
995 
996 	debug_print("textview_add_part: adding: %s\n", buf);
997 
998 	if (mimeinfo->mime_type != MIME_TEXT &&
999 	    mimeinfo->mime_type != MIME_TEXT_HTML) {
1000 		if (mimeinfo->mime_type == MIME_IMAGE &&
1001 		    prefs_common.inline_image) {
1002 			GdkPixbuf *pixbuf;
1003 			GError *error = NULL;
1004 			gchar *filename;
1005 			RemoteURI *uri;
1006 			gchar *uri_str;
1007 
1008 			gtk_text_buffer_insert(buffer, &iter, "\n", 1);
1009 			textview_add_part_widget(textview, &iter, mimeinfo, buf);
1010 
1011 			filename = procmime_get_tmp_file_name(mimeinfo);
1012 			if (procmime_get_part_fp(filename, fp, mimeinfo) < 0) {
1013 				g_warning("Can't get the image file.");
1014 				g_free(filename);
1015 				return;
1016 			}
1017 
1018 			pixbuf = gdk_pixbuf_new_from_file(filename, &error);
1019 			if (error != NULL) {
1020 				g_warning("%s\n", error->message);
1021 				g_error_free(error);
1022 			}
1023 			if (!pixbuf) {
1024 				g_warning("Can't load the image.");
1025 				g_free(filename);
1026 				return;
1027 			}
1028 
1029 			{
1030 				GdkPixbuf *rotated;
1031 
1032 				rotated = imageview_get_rotated_pixbuf(pixbuf);
1033 				g_object_unref(pixbuf);
1034 				pixbuf = rotated;
1035 			}
1036 			if (prefs_common.resize_image) {
1037 				GdkPixbuf *scaled;
1038 
1039 				scaled = imageview_get_resized_pixbuf
1040 					(pixbuf, textview->text, 8);
1041 				g_object_unref(pixbuf);
1042 				pixbuf = scaled;
1043 			}
1044 
1045 			uri_str = g_filename_to_uri(filename, NULL, NULL);
1046 			if (uri_str) {
1047 				uri = g_new(RemoteURI, 1);
1048 				uri->uri = uri_str;
1049 				uri->filename =
1050 					procmime_get_part_file_name(mimeinfo);
1051 				uri->start = gtk_text_iter_get_offset(&iter);
1052 				uri->end = uri->start + 1;
1053 				textview->uri_list =
1054 					g_slist_append(textview->uri_list, uri);
1055 			}
1056 			gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf);
1057 			gtk_text_buffer_insert(buffer, &iter, "\n", 1);
1058 
1059 			g_object_unref(pixbuf);
1060 			g_free(filename);
1061 		} else if (prefs_common.show_attached_files_first) {
1062 			gint count;
1063 			gint prev_pos, new_pos;
1064 
1065 			mark = gtk_text_buffer_get_mark(buffer, "attach-file-pos");
1066 
1067 			gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1068 			prev_pos = gtk_text_iter_get_offset(&iter);
1069 			count = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(mark), "attach-file-count"));
1070 			if (count == 0) {
1071 				textview_insert_pad(textview, &iter, 4);
1072 				gtk_text_buffer_move_mark(buffer, mark, &iter);
1073 			}
1074 			textview_add_part_widget(textview, &iter, mimeinfo, buf);
1075 			gtk_text_buffer_move_mark(buffer, mark, &iter);
1076 			new_pos = gtk_text_iter_get_offset(&iter);
1077 			textview_uri_list_update_offsets(textview, new_pos, new_pos - prev_pos);
1078 			g_object_set_data(G_OBJECT(mark), "attach-file-count", GINT_TO_POINTER(count + 1));
1079 			gtk_text_buffer_get_end_iter(buffer, &iter);
1080 		} else {
1081 			gtk_text_buffer_insert(buffer, &iter, "\n", 1);
1082 			textview_add_part_widget(textview, &iter, mimeinfo, buf);
1083 		}
1084 	} else {
1085 		/* text part */
1086 		gtk_text_buffer_insert(buffer, &iter, "\n", 1);
1087 		if (mimeinfo->mime_type == MIME_TEXT_HTML ||
1088 		    (!mimeinfo->main && mimeinfo->parent &&
1089 		     mimeinfo->parent->children != mimeinfo))
1090 			textview_add_part_widget(textview, &iter, mimeinfo, buf);
1091 		textview_write_body(textview, mimeinfo, fp, charset);
1092 	}
1093 }
1094 
1095 #if USE_GPGME
textview_add_sig_part(TextView * textview,MimeInfo * mimeinfo)1096 static void textview_add_sig_part(TextView *textview, MimeInfo *mimeinfo)
1097 {
1098 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1099 	GtkTextBuffer *buffer;
1100 	GtkTextIter iter;
1101 	gchar buf[BUFFSIZE];
1102 	const gchar *color;
1103 	const gchar *sigstatus;
1104 	const gchar *sigstatus_full;
1105 	const gchar *type;
1106 
1107 	if (mimeinfo) {
1108 		sigstatus = mimeinfo->sigstatus;
1109 		sigstatus_full = mimeinfo->sigstatus_full;
1110 		type = mimeinfo->content_type;
1111 	} else if (textview->messageview->msginfo->encinfo) {
1112 		sigstatus = textview->messageview->msginfo->encinfo->sigstatus;
1113 		sigstatus_full =
1114 			textview->messageview->msginfo->encinfo->sigstatus_full;
1115 		type = "signature";
1116 	} else
1117 		return;
1118 
1119 	if (!sigstatus)
1120 		return;
1121 
1122 	g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n", type, sigstatus);
1123 
1124 	if (!strcmp(sigstatus, _("Good signature")))
1125 		color = "good-signature";
1126 	else if (!strcmp(sigstatus, _("Valid signature (untrusted key)")))
1127 		color = "untrusted-signature";
1128 	else if (!strcmp(sigstatus, _("BAD signature")))
1129 		color = "bad-signature";
1130 	else
1131 		color = "nocheck-signature";
1132 
1133 	buffer = gtk_text_view_get_buffer(text);
1134 	gtk_text_buffer_get_end_iter(buffer, &iter);
1135 	gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, buf, -1,
1136 						 color, "mimepart", NULL);
1137 	if (sigstatus_full)
1138 		gtk_text_buffer_insert_with_tags_by_name
1139 			(buffer, &iter, sigstatus_full, -1, "mimepart", NULL);
1140 }
1141 #endif
1142 
textview_add_parts(TextView * textview,MimeInfo * mimeinfo,FILE * fp)1143 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
1144 {
1145 	gint level;
1146 
1147 	g_return_if_fail(mimeinfo != NULL);
1148 	g_return_if_fail(fp != NULL);
1149 
1150 	level = mimeinfo->level;
1151 
1152 	for (;;) {
1153 		if (mimeinfo->mime_type == MIME_MULTIPART &&
1154 		    mimeinfo->content_type &&
1155 		    !g_ascii_strcasecmp(mimeinfo->content_type,
1156 					"multipart/alternative")) {
1157 			MimeInfo *preferred_part = mimeinfo->children;
1158 			MimeInfo *child;
1159 
1160 			if (prefs_common.alt_prefer_html) {
1161 				for (child = mimeinfo->children; child != NULL; child = child->next) {
1162 					if (child->mime_type == MIME_TEXT_HTML) {
1163 						preferred_part = child;
1164 						break;
1165 					}
1166 				}
1167 			}
1168 
1169 			if (preferred_part) {
1170 				textview_add_part(textview, preferred_part, fp);
1171 				mimeinfo = preferred_part;
1172 				while (mimeinfo->next)
1173 					mimeinfo = mimeinfo->next;
1174 			}
1175 		} else {
1176 			textview_add_part(textview, mimeinfo, fp);
1177 		}
1178 
1179 		mimeinfo = procmime_mimeinfo_next(mimeinfo);
1180 
1181 		if (!mimeinfo || mimeinfo->level <= level)
1182 			break;
1183 	}
1184 }
1185 
textview_write_error(TextView * textview,const gchar * msg)1186 static void textview_write_error(TextView *textview, const gchar *msg)
1187 {
1188 	GtkTextBuffer *buffer;
1189 	GtkTextIter iter;
1190 
1191 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1192 	gtk_text_buffer_get_end_iter(buffer, &iter);
1193 	gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, msg, -1,
1194 						 "error", NULL);
1195 }
1196 
textview_show_error(TextView * textview)1197 void textview_show_error(TextView *textview)
1198 {
1199 	textview_set_font(textview, NULL);
1200 	textview_clear(textview);
1201 	textview_write_error(textview, _("This message can't be displayed.\n"));
1202 }
1203 
textview_write_body(TextView * textview,MimeInfo * mimeinfo,FILE * fp,const gchar * charset)1204 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
1205 				FILE *fp, const gchar *charset)
1206 {
1207 	FILE *tmpfp;
1208 	gchar buf[BUFFSIZE];
1209 	CodeConverter *conv;
1210 
1211 	conv = conv_code_converter_new(charset, NULL);
1212 
1213 	tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
1214 	if (tmpfp) {
1215 		if (mimeinfo->mime_type == MIME_TEXT_HTML &&
1216 		    prefs_common.render_html)
1217 			textview_show_html(textview, tmpfp, conv);
1218 		else
1219 			while (fgets(buf, sizeof(buf), tmpfp) != NULL)
1220 				textview_write_line(textview, buf, conv);
1221 		fclose(tmpfp);
1222 	} else {
1223 		textview_write_error
1224 			(textview,
1225 			 _("The body text couldn't be displayed because "
1226 			   "writing to temporary file failed.\n"));
1227 	}
1228 
1229 	conv_code_converter_destroy(conv);
1230 }
1231 
textview_show_html(TextView * textview,FILE * fp,CodeConverter * conv)1232 static void textview_show_html(TextView *textview, FILE *fp,
1233 			       CodeConverter *conv)
1234 {
1235 	HTMLParser *parser;
1236 	const gchar *str;
1237 
1238 	parser = html_parser_new(fp, conv);
1239 	g_return_if_fail(parser != NULL);
1240 
1241 	while ((str = html_parse(parser)) != NULL) {
1242 		if (parser->href != NULL)
1243 			textview_write_link(textview, str, parser->href, NULL);
1244 		else
1245 			textview_write_line(textview, str, NULL);
1246 	}
1247 	textview_write_line(textview, "\n", NULL);
1248 
1249 	html_parser_destroy(parser);
1250 }
1251 
1252 /* get_uri_part() - retrieves a URI starting from scanpos.
1253 		    Returns TRUE if succesful */
get_uri_part(const gchar * start,const gchar * scanpos,const gchar ** bp,const gchar ** ep)1254 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
1255 			     const gchar **bp, const gchar **ep)
1256 {
1257 	const gchar *ep_;
1258 
1259 	g_return_val_if_fail(start != NULL, FALSE);
1260 	g_return_val_if_fail(scanpos != NULL, FALSE);
1261 	g_return_val_if_fail(bp != NULL, FALSE);
1262 	g_return_val_if_fail(ep != NULL, FALSE);
1263 
1264 	*bp = scanpos;
1265 
1266 	/* find end point of URI */
1267 	for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
1268 		if (!g_ascii_isgraph(*ep_) ||
1269 		    !isascii(*(const guchar *)ep_) ||
1270 		    strchr("()<>{}[]\"", *ep_))
1271 			break;
1272 	}
1273 
1274 	/* no punctuation at end of string */
1275 
1276 	/* FIXME: this stripping of trailing punctuations may bite with other URIs.
1277 	 * should pass some URI type to this function and decide on that whether
1278 	 * to perform punctuation stripping */
1279 
1280 #define IS_REAL_PUNCT(ch)	(g_ascii_ispunct(ch) && !strchr("/?=", ch))
1281 
1282 	for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
1283 		;
1284 
1285 #undef IS_REAL_PUNCT
1286 
1287 	*ep = ep_;
1288 
1289 	return TRUE;
1290 }
1291 
make_uri_string(const gchar * bp,const gchar * ep)1292 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
1293 {
1294 	return g_strndup(bp, ep - bp);
1295 }
1296 
make_http_uri_string(const gchar * bp,const gchar * ep)1297 static gchar *make_http_uri_string(const gchar *bp, const gchar *ep)
1298 {
1299 	gchar *tmp;
1300 	gchar *result;
1301 
1302 	tmp = g_strndup(bp, ep - bp);
1303 	result = g_strconcat("http://", tmp, NULL);
1304 	g_free(tmp);
1305 
1306 	return result;
1307 }
1308 
1309 /* valid mail address characters */
1310 #define IS_RFC822_CHAR(ch) \
1311 	(isascii(ch) && \
1312 	 (ch) > 32   && \
1313 	 (ch) != 127 && \
1314 	 !g_ascii_isspace(ch) && \
1315 	 !strchr("(),;<>\"", (ch)))
1316 
1317 /* alphabet and number within 7bit ASCII */
1318 #define IS_ASCII_ALNUM(ch)	(isascii(ch) && g_ascii_isalnum(ch))
1319 
1320 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
get_email_part(const gchar * start,const gchar * scanpos,const gchar ** bp,const gchar ** ep)1321 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
1322 			       const gchar **bp, const gchar **ep)
1323 {
1324 	/* more complex than the uri part because we need to scan back and forward starting from
1325 	 * the scan position. */
1326 	gboolean result = FALSE;
1327 	const gchar *bp_;
1328 	const gchar *ep_;
1329 
1330 	g_return_val_if_fail(start != NULL, FALSE);
1331 	g_return_val_if_fail(scanpos != NULL, FALSE);
1332 	g_return_val_if_fail(bp != NULL, FALSE);
1333 	g_return_val_if_fail(ep != NULL, FALSE);
1334 
1335 	/* scan start of address */
1336 	for (bp_ = scanpos - 1;
1337 	     bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
1338 		;
1339 
1340 	/* TODO: should start with an alnum? */
1341 	bp_++;
1342 	for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
1343 		;
1344 
1345 	if (bp_ != scanpos) {
1346 		/* scan end of address */
1347 		for (ep_ = scanpos + 1;
1348 		     *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
1349 			;
1350 
1351 		/* TODO: really should terminate with an alnum? */
1352 		for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
1353 		     --ep_)
1354 			;
1355 		ep_++;
1356 
1357 		if (ep_ > scanpos + 1) {
1358 			*ep = ep_;
1359 			*bp = bp_;
1360 			result = TRUE;
1361 		}
1362 	}
1363 
1364 	return result;
1365 }
1366 
1367 #undef IS_ASCII_ALNUM
1368 #undef IS_RFC822_CHAR
1369 
make_email_string(const gchar * bp,const gchar * ep)1370 static gchar *make_email_string(const gchar *bp, const gchar *ep)
1371 {
1372 	/* returns a mailto: URI; mailto: is also used to detect the
1373 	 * uri type later on in the button_pressed signal handler */
1374 	gchar *tmp, *enc;
1375 	gchar *result;
1376 
1377 	tmp = g_strndup(bp, ep - bp);
1378 	enc = uriencode_for_mailto(tmp);
1379 	result = g_strconcat("mailto:", enc, NULL);
1380 	g_free(enc);
1381 	g_free(tmp);
1382 
1383 	return result;
1384 }
1385 
1386 #define ADD_TXT_POS(bp_, ep_, pti_) \
1387 { \
1388 	struct txtpos *last; \
1389  \
1390 	last = g_new(struct txtpos, 1); \
1391 	last->bp = (bp_); \
1392 	last->ep = (ep_); \
1393 	last->pti = (pti_); \
1394 	txtpos_list = g_slist_append(txtpos_list, last); \
1395 }
1396 
1397 /* textview_make_clickable_parts() - colorizes clickable parts */
textview_make_clickable_parts(TextView * textview,const gchar * fg_tag,const gchar * uri_tag,const gchar * linebuf)1398 static void textview_make_clickable_parts(TextView *textview,
1399 					  const gchar *fg_tag,
1400 					  const gchar *uri_tag,
1401 					  const gchar *linebuf)
1402 {
1403 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1404 	GtkTextBuffer *buffer;
1405 	GtkTextIter iter;
1406 
1407 	/* parse table - in order of priority */
1408 	struct table {
1409 		const gchar *needle; /* token */
1410 
1411 		/* token search function */
1412 		gchar    *(*search)	(const gchar *haystack,
1413 					 const gchar *needle);
1414 		/* part parsing function */
1415 		gboolean  (*parse)	(const gchar *start,
1416 					 const gchar *scanpos,
1417 					 const gchar **bp_,
1418 					 const gchar **ep_);
1419 		/* part to URI function */
1420 		gchar    *(*build_uri)	(const gchar *bp,
1421 					 const gchar *ep);
1422 	};
1423 
1424 	static struct table parser[] = {
1425 		{"http://",  strcasestr, get_uri_part,   make_uri_string},
1426 		{"https://", strcasestr, get_uri_part,   make_uri_string},
1427 		{"ftp://",   strcasestr, get_uri_part,   make_uri_string},
1428 		{"www.",     strcasestr, get_uri_part,   make_http_uri_string},
1429 		{"mailto:",  strcasestr, get_uri_part,   make_uri_string},
1430 		{"@",        strcasestr, get_email_part, make_email_string}
1431 	};
1432 	const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1433 
1434 	/* flags for search optimization */
1435 	gboolean do_search[] = {TRUE, TRUE, TRUE, TRUE, TRUE, TRUE};
1436 
1437 	gint  n;
1438 	const gchar *walk, *bp, *ep;
1439 
1440 	struct txtpos {
1441 		const gchar	*bp, *ep;	/* text position */
1442 		gint		 pti;		/* index in parse table */
1443 	};
1444 	GSList *txtpos_list = NULL;
1445 
1446 	buffer = gtk_text_view_get_buffer(text);
1447 	gtk_text_buffer_get_end_iter(buffer, &iter);
1448 
1449 	/* parse for clickable parts, and build a list of begin and
1450 	   end positions  */
1451 	for (walk = linebuf, n = 0;;) {
1452 		gint last_index = PARSE_ELEMS;
1453 		const gchar *scanpos = NULL;
1454 
1455 		/* FIXME: this looks phony. scanning for anything in the
1456 		   parse table */
1457 		for (n = 0; n < PARSE_ELEMS; n++) {
1458 			const gchar *tmp;
1459 
1460 			if (do_search[n]) {
1461 				tmp = parser[n].search(walk, parser[n].needle);
1462 				if (tmp) {
1463 					if (scanpos == NULL || tmp < scanpos) {
1464 						scanpos = tmp;
1465 						last_index = n;
1466 					}
1467 				} else
1468 					do_search[n] = FALSE;
1469 			}
1470 		}
1471 
1472 		if (scanpos) {
1473 			/* check if URI can be parsed */
1474 			if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1475 			    && (ep - bp - 1) > strlen(parser[last_index].needle)) {
1476 					ADD_TXT_POS(bp, ep, last_index);
1477 					walk = ep;
1478 			} else
1479 				walk = scanpos +
1480 					strlen(parser[last_index].needle);
1481 		} else
1482 			break;
1483 	}
1484 
1485 	/* colorize this line */
1486 	if (txtpos_list) {
1487 		const gchar *normal_text = linebuf;
1488 		GSList *cur;
1489 
1490 		/* insert URIs */
1491 		for (cur = txtpos_list; cur != NULL; cur = cur->next) {
1492 			struct txtpos *pos = (struct txtpos *)cur->data;
1493 			RemoteURI *uri;
1494 
1495 			uri = g_new(RemoteURI, 1);
1496 			if (pos->bp - normal_text > 0)
1497 				gtk_text_buffer_insert_with_tags_by_name
1498 					(buffer, &iter,
1499 					 normal_text,
1500 					 pos->bp - normal_text,
1501 					 fg_tag, NULL);
1502 			uri->uri = parser[pos->pti].build_uri(pos->bp, pos->ep);
1503 			uri->filename = NULL;
1504 			uri->start = gtk_text_iter_get_offset(&iter);
1505 			gtk_text_buffer_insert_with_tags_by_name
1506 				(buffer, &iter, pos->bp, pos->ep - pos->bp,
1507 				 uri_tag, fg_tag, NULL);
1508 			uri->end = gtk_text_iter_get_offset(&iter);
1509 			textview->uri_list =
1510 				g_slist_append(textview->uri_list, uri);
1511 			normal_text = pos->ep;
1512 
1513 			g_free(pos);
1514 		}
1515 
1516 		if (*normal_text)
1517 			gtkut_text_buffer_insert_with_tag_by_name
1518 				(buffer, &iter, normal_text, -1, fg_tag);
1519 
1520 		g_slist_free(txtpos_list);
1521 	} else {
1522 		gtkut_text_buffer_insert_with_tag_by_name
1523 			(buffer, &iter, linebuf, -1, fg_tag);
1524 	}
1525 }
1526 
1527 #undef ADD_TXT_POS
1528 
textview_write_line(TextView * textview,const gchar * str,CodeConverter * conv)1529 static void textview_write_line(TextView *textview, const gchar *str,
1530 				CodeConverter *conv)
1531 {
1532 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1533 	GtkTextBuffer *buffer;
1534 	GtkTextIter iter;
1535 	gchar *buf;
1536 	gchar *fg_color = NULL;
1537 	gint quotelevel = -1;
1538 	gchar quote_tag_str[10];
1539 
1540 	buffer = gtk_text_view_get_buffer(text);
1541 	gtk_text_buffer_get_end_iter(buffer, &iter);
1542 
1543 	if (conv) {
1544 		buf = conv_convert(conv, str);
1545 		if (!buf)
1546 			buf = conv_utf8todisp(str, NULL);
1547 	} else
1548 		buf = g_strdup(str);
1549 
1550 	strcrchomp(buf);
1551 	//if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1552 
1553 	/* change color of quotation
1554 	   >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1555 	   Up to 3 levels of quotations are detected, and each
1556 	   level is colored using a different color. */
1557 	if (prefs_common.enable_color && strchr(buf, '>')) {
1558 		quotelevel = get_quote_level(buf);
1559 
1560 		/* set up the correct foreground color */
1561 		if (quotelevel > 2) {
1562 			/* recycle colors */
1563 			if (prefs_common.recycle_quote_colors)
1564 				quotelevel %= 3;
1565 			else
1566 				quotelevel = 2;
1567 		}
1568 	}
1569 
1570 	if (quotelevel != -1) {
1571 		g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1572 			   "quote%d", quotelevel);
1573 		fg_color = quote_tag_str;
1574 	}
1575 
1576 	if (prefs_common.enable_color)
1577 		textview_make_clickable_parts(textview, fg_color, "link", buf);
1578 	else
1579 		textview_make_clickable_parts(textview, fg_color, NULL, buf);
1580 
1581 	g_free(buf);
1582 }
1583 
textview_write_link(TextView * textview,const gchar * str,const gchar * uri,CodeConverter * conv)1584 static void textview_write_link(TextView *textview, const gchar *str,
1585 				const gchar *uri, CodeConverter *conv)
1586 {
1587 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1588 	GtkTextBuffer *buffer;
1589 	GtkTextIter iter;
1590 	gchar *buf;
1591 	gchar *bufp;
1592 	RemoteURI *r_uri;
1593 
1594 	if (!str || *str == '\0')
1595 		return;
1596 	if (!uri)
1597 		return;
1598 
1599 	buffer = gtk_text_view_get_buffer(text);
1600 	gtk_text_buffer_get_end_iter(buffer, &iter);
1601 
1602 	if (conv) {
1603 		buf = conv_convert(conv, str);
1604 		if (!buf)
1605 			buf = conv_utf8todisp(str, NULL);
1606 	} else
1607 		buf = g_strdup(str);
1608 
1609 	if (g_utf8_validate(buf, -1, NULL) == FALSE) {
1610 		g_free(buf);
1611 		return;
1612 	}
1613 
1614 	strcrchomp(buf);
1615 
1616 	for (bufp = buf; *bufp != '\0'; bufp = g_utf8_next_char(bufp)) {
1617 		gunichar ch;
1618 
1619 		ch = g_utf8_get_char(bufp);
1620 		if (!g_unichar_isspace(ch))
1621 			break;
1622 	}
1623 	if (bufp > buf)
1624 		gtk_text_buffer_insert(buffer, &iter, buf, bufp - buf);
1625 
1626 	if (gtk_text_iter_ends_tag(&iter, textview->link_tag))
1627 		gtk_text_buffer_insert(buffer, &iter, " ", 1);
1628 
1629 	r_uri = g_new(RemoteURI, 1);
1630 	r_uri->uri = g_strstrip(g_strdup(uri));
1631 	r_uri->filename = NULL;
1632 	r_uri->start = gtk_text_iter_get_offset(&iter);
1633 	gtk_text_buffer_insert_with_tags_by_name
1634 		(buffer, &iter, bufp, -1, "link", NULL);
1635 	r_uri->end = gtk_text_iter_get_offset(&iter);
1636 	textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1637 
1638 	g_free(buf);
1639 }
1640 
textview_clear(TextView * textview)1641 void textview_clear(TextView *textview)
1642 {
1643 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1644 	GtkTextBuffer *buffer;
1645 
1646 	buffer = gtk_text_view_get_buffer(text);
1647 	gtk_text_buffer_set_text(buffer, "", -1);
1648 
1649 	/* workaround for the assertion failure in
1650 	   gtk_text_view_validate_onscreen() */
1651 	text->vadjustment->value = 0.0;
1652 
1653 	STATUSBAR_POP(textview);
1654 	textview_uri_list_remove_all(textview->uri_list);
1655 	textview->uri_list = NULL;
1656 
1657 	textview->body_pos = 0;
1658 }
1659 
textview_destroy(TextView * textview)1660 void textview_destroy(TextView *textview)
1661 {
1662 	GtkTextBuffer *buffer;
1663 	GtkClipboard *clipboard;
1664 
1665 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1666 	clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1667 	gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
1668 
1669 	gtk_widget_destroy(textview->popup_menu);
1670 
1671 	textview_uri_list_remove_all(textview->uri_list);
1672 	textview->uri_list = NULL;
1673 
1674 	g_free(textview);
1675 }
1676 
textview_set_all_headers(TextView * textview,gboolean all_headers)1677 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1678 {
1679 	textview->show_all_headers = all_headers;
1680 }
1681 
textview_set_font(TextView * textview,const gchar * codeset)1682 void textview_set_font(TextView *textview, const gchar *codeset)
1683 {
1684 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1685 
1686 	if (prefs_common.textfont) {
1687 		PangoFontDescription *font_desc;
1688 		font_desc = pango_font_description_from_string
1689 			(prefs_common.textfont);
1690 		if (font_desc) {
1691 			gtk_widget_modify_font(textview->text, font_desc);
1692 			pango_font_description_free(font_desc);
1693 		}
1694 	}
1695 
1696 	gtk_text_view_set_pixels_above_lines(text, prefs_common.line_space - prefs_common.line_space / 2);
1697 	gtk_text_view_set_pixels_below_lines(text, prefs_common.line_space / 2);
1698 	gtk_text_view_set_pixels_inside_wrap(text, prefs_common.line_space);
1699 }
1700 
textview_set_position(TextView * textview,gint pos)1701 void textview_set_position(TextView *textview, gint pos)
1702 {
1703 	GtkTextBuffer *buffer;
1704 	GtkTextIter iter;
1705 
1706 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1707 	gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1708 	gtk_text_buffer_place_cursor(buffer, &iter);
1709 }
1710 
textview_scan_header(TextView * textview,FILE * fp,const gchar * encoding)1711 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp,
1712 				       const gchar *encoding)
1713 {
1714 	g_return_val_if_fail(fp != NULL, NULL);
1715 
1716 	if (textview->show_all_headers)
1717 		return procheader_get_header_array_asis(fp, encoding);
1718 
1719 	if (!prefs_common.display_header) {
1720 		gchar buf[BUFFSIZE];
1721 
1722 		while (fgets(buf, sizeof(buf), fp) != NULL)
1723 			if (buf[0] == '\r' || buf[0] == '\n') break;
1724 		return NULL;
1725 	}
1726 
1727 	return procheader_get_header_array_for_display(fp, encoding);
1728 }
1729 
textview_size_allocate_cb(GtkWidget * widget,GtkAllocation * allocation,gpointer data)1730 static void textview_size_allocate_cb(GtkWidget *widget,
1731 				      GtkAllocation *allocation, gpointer data)
1732 {
1733 	GtkWidget *child = GTK_WIDGET(data);
1734 	gint new_width;
1735 
1736 	/* g_print("textview_size_allocate_cb: (%d, %d)\n", allocation->width, allocation->height); */
1737 	new_width = allocation->width - 14;
1738 	if (new_width < -1)
1739 		new_width = -1;
1740 	gtk_widget_set_size_request(child, new_width, 16);
1741 }
1742 
textview_hline_destroy_cb(GtkObject * object,gpointer data)1743 void textview_hline_destroy_cb(GtkObject *object, gpointer data)
1744 {
1745 	GtkWidget *text = GTK_WIDGET(data);
1746 
1747 	g_signal_handlers_disconnect_by_func(text, textview_size_allocate_cb,
1748 					     object);
1749 }
1750 
textview_show_header(TextView * textview,GPtrArray * headers)1751 static void textview_show_header(TextView *textview, GPtrArray *headers)
1752 {
1753 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1754 	GtkTextBuffer *buffer;
1755 	GtkTextIter iter;
1756 	Header *header;
1757 	gint i;
1758 	GtkTextMark *mark;
1759 
1760 	g_return_if_fail(headers != NULL);
1761 
1762 	buffer = gtk_text_view_get_buffer(text);
1763 	gtk_text_buffer_get_end_iter(buffer, &iter);
1764 
1765 	for (i = 0; i < headers->len; i++) {
1766 		header = g_ptr_array_index(headers, i);
1767 		g_return_if_fail(header->name != NULL);
1768 
1769 		gtk_text_buffer_insert_with_tags_by_name
1770 			(buffer, &iter, header->name, -1,
1771 			 "header_title", "header", NULL);
1772 		gtk_text_buffer_insert_with_tags_by_name
1773 			(buffer, &iter, ":", 1,
1774 			 "header_title", "header", NULL);
1775 
1776 		if (!g_ascii_strcasecmp(header->name, "Subject") ||
1777 		    !g_ascii_strcasecmp(header->name, "From")    ||
1778 		    !g_ascii_strcasecmp(header->name, "To")      ||
1779 		    !g_ascii_strcasecmp(header->name, "Cc"))
1780 			unfold_line(header->body);
1781 
1782 		if (prefs_common.enable_color &&
1783 		    (!strncmp(header->name, "X-Mailer", 8) ||
1784 		     !strncmp(header->name, "X-Newsreader", 12)) &&
1785 		    strstr(header->body, "Sylpheed") != NULL) {
1786 			gtk_text_buffer_insert_with_tags_by_name
1787 				(buffer, &iter, header->body, -1,
1788 				 "header", "emphasis", NULL);
1789 		} else if (prefs_common.enable_color) {
1790 			textview_make_clickable_parts
1791 				(textview, "header", "link", header->body);
1792 		} else {
1793 			textview_make_clickable_parts
1794 				(textview, "header", NULL, header->body);
1795 		}
1796 		gtk_text_buffer_get_end_iter(buffer, &iter);
1797 		gtk_text_buffer_insert_with_tags_by_name
1798 			(buffer, &iter, "\n", 1, "header", NULL);
1799 	}
1800 
1801 	mark = gtk_text_buffer_get_mark(buffer, "attach-file-pos");
1802 	gtk_text_buffer_move_mark(buffer, mark, &iter);
1803 	g_object_set_data(G_OBJECT(mark), "attach-file-count", GINT_TO_POINTER(0));
1804 
1805 	textview_insert_border(textview, &iter, 0);
1806 }
1807 
textview_insert_border(TextView * textview,GtkTextIter * iter,gint padding)1808 static void textview_insert_border(TextView *textview, GtkTextIter *iter,
1809 				   gint padding)
1810 {
1811 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1812 	GtkTextBuffer *buffer;
1813 	GtkTextChildAnchor *anchor;
1814 	GtkWidget *vbox;
1815 	GtkWidget *pad;
1816 	GtkWidget *hline;
1817 
1818 	buffer = gtk_text_view_get_buffer(text);
1819 
1820 	anchor = gtk_text_buffer_create_child_anchor(buffer, iter);
1821 	vbox = gtk_vbox_new(FALSE, 0);
1822 	if (padding > 0) {
1823 		pad = gtk_vbox_new(FALSE, 0);
1824 		gtk_box_pack_start(GTK_BOX(vbox), pad, FALSE, FALSE, 0);
1825 		gtk_widget_set_size_request(pad, -1, padding);
1826 	}
1827 	hline = gtk_hseparator_new();
1828 	gtk_box_pack_start(GTK_BOX(vbox), hline, FALSE, FALSE, 0);
1829 	gtk_widget_show_all(vbox);
1830 	gtk_widget_set_size_request(hline, 320, 16);
1831 	gtk_text_view_add_child_at_anchor(text, vbox, anchor);
1832 	g_signal_connect(G_OBJECT(hline), "destroy",
1833 			 G_CALLBACK(textview_hline_destroy_cb), text);
1834 	g_signal_connect(G_OBJECT(text), "size-allocate",
1835 			 G_CALLBACK(textview_size_allocate_cb), hline);
1836 }
1837 
textview_insert_pad(TextView * textview,GtkTextIter * iter,gint padding)1838 static void textview_insert_pad(TextView *textview, GtkTextIter *iter,
1839 				gint padding)
1840 {
1841 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1842 	GtkTextBuffer *buffer;
1843 	GtkTextChildAnchor *anchor;
1844 	GtkWidget *vbox;
1845 
1846 	buffer = gtk_text_view_get_buffer(text);
1847 
1848 	anchor = gtk_text_buffer_create_child_anchor(buffer, iter);
1849 	vbox = gtk_vbox_new(FALSE, 0);
1850 	gtk_widget_set_size_request(vbox, -1, padding);
1851 	gtk_widget_show_all(vbox);
1852 	gtk_text_view_add_child_at_anchor(text, vbox, anchor);
1853 
1854 	gtk_text_buffer_insert(buffer, iter, "\n", 1);
1855 }
1856 
textview_search_string(TextView * textview,const gchar * str,gboolean case_sens)1857 gboolean textview_search_string(TextView *textview, const gchar *str,
1858 				gboolean case_sens)
1859 {
1860 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1861 	GtkTextBuffer *buffer;
1862 	GtkTextIter iter, match_pos;
1863 	GtkTextMark *mark;
1864 	gint len;
1865 
1866 	g_return_val_if_fail(str != NULL, FALSE);
1867 
1868 	buffer = gtk_text_view_get_buffer(text);
1869 
1870 	len = g_utf8_strlen(str, -1);
1871 	g_return_val_if_fail(len >= 0, FALSE);
1872 
1873 	gtk_text_buffer_get_selection_bounds(buffer, NULL, &iter);
1874 
1875 	if (gtkut_text_buffer_find(buffer, &iter, str, case_sens,
1876 				   &match_pos)) {
1877 		GtkTextIter end = match_pos;
1878 
1879 		gtk_text_iter_forward_chars(&end, len);
1880 		/* place "insert" at the last character */
1881 		gtk_text_buffer_select_range(buffer, &end, &match_pos);
1882 		mark = gtk_text_buffer_get_insert(buffer);
1883 		gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1884 		return TRUE;
1885 	}
1886 
1887 	return FALSE;
1888 }
1889 
textview_search_string_backward(TextView * textview,const gchar * str,gboolean case_sens)1890 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1891 					 gboolean case_sens)
1892 {
1893 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1894 	GtkTextBuffer *buffer;
1895 	GtkTextIter iter, match_pos;
1896 	GtkTextMark *mark;
1897 	gint len;
1898 
1899 	g_return_val_if_fail(str != NULL, FALSE);
1900 
1901 	buffer = gtk_text_view_get_buffer(text);
1902 
1903 	len = g_utf8_strlen(str, -1);
1904 	g_return_val_if_fail(len >= 0, FALSE);
1905 
1906 	gtk_text_buffer_get_selection_bounds(buffer, &iter, NULL);
1907 
1908 	if (gtkut_text_buffer_find_backward(buffer, &iter, str, case_sens,
1909 					    &match_pos)) {
1910 		GtkTextIter end = match_pos;
1911 
1912 		gtk_text_iter_forward_chars(&end, len);
1913 		gtk_text_buffer_select_range(buffer, &match_pos, &end);
1914 		mark = gtk_text_buffer_get_insert(buffer);
1915 		gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1916 		return TRUE;
1917 	}
1918 
1919 	return FALSE;
1920 }
1921 
textview_scroll_one_line(TextView * textview,gboolean up)1922 void textview_scroll_one_line(TextView *textview, gboolean up)
1923 {
1924 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1925 	GtkAdjustment *vadj = text->vadjustment;
1926 	gfloat upper;
1927 
1928 	if (prefs_common.enable_smooth_scroll) {
1929 		textview_smooth_scroll_one_line(textview, up);
1930 		return;
1931 	}
1932 
1933 	if (!up) {
1934 		upper = vadj->upper - vadj->page_size;
1935 		if (vadj->value < upper) {
1936 			vadj->value += vadj->step_increment;
1937 			vadj->value = MIN(vadj->value, upper);
1938 			g_signal_emit_by_name(G_OBJECT(vadj),
1939 					      "value_changed", 0);
1940 		}
1941 	} else {
1942 		if (vadj->value > 0.0) {
1943 			vadj->value -= vadj->step_increment;
1944 			vadj->value = MAX(vadj->value, 0.0);
1945 			g_signal_emit_by_name(G_OBJECT(vadj),
1946 					      "value_changed", 0);
1947 		}
1948 	}
1949 }
1950 
textview_scroll_page(TextView * textview,gboolean up)1951 gboolean textview_scroll_page(TextView *textview, gboolean up)
1952 {
1953 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1954 	GtkAdjustment *vadj = text->vadjustment;
1955 	gfloat upper;
1956 	gfloat page_incr;
1957 
1958 	if (prefs_common.enable_smooth_scroll)
1959 		return textview_smooth_scroll_page(textview, up);
1960 
1961 	if (prefs_common.scroll_halfpage)
1962 		page_incr = vadj->page_increment / 2;
1963 	else
1964 		page_incr = vadj->page_increment;
1965 
1966 	if (!up) {
1967 		upper = vadj->upper - vadj->page_size;
1968 		if (vadj->value < upper) {
1969 			vadj->value += page_incr;
1970 			vadj->value = MIN(vadj->value, upper);
1971 			g_signal_emit_by_name(G_OBJECT(vadj),
1972 					      "value_changed", 0);
1973 		} else
1974 			return FALSE;
1975 	} else {
1976 		if (vadj->value > 0.0) {
1977 			vadj->value -= page_incr;
1978 			vadj->value = MAX(vadj->value, 0.0);
1979 			g_signal_emit_by_name(G_OBJECT(vadj),
1980 					      "value_changed", 0);
1981 		} else
1982 			return FALSE;
1983 	}
1984 
1985 	return TRUE;
1986 }
1987 
textview_smooth_scroll_do(TextView * textview,gfloat old_value,gfloat last_value,gint step)1988 static void textview_smooth_scroll_do(TextView *textview,
1989 				      gfloat old_value, gfloat last_value,
1990 				      gint step)
1991 {
1992 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1993 	GtkAdjustment *vadj = text->vadjustment;
1994 	gint change_value;
1995 	gboolean up;
1996 	gint i;
1997 
1998 	if (old_value < last_value) {
1999 		change_value = last_value - old_value;
2000 		up = FALSE;
2001 	} else {
2002 		change_value = old_value - last_value;
2003 		up = TRUE;
2004 	}
2005 
2006 	/* gdk_key_repeat_disable(); */
2007 
2008 	g_signal_handlers_block_by_func(vadj, textview_adj_value_changed,
2009 					textview);
2010 
2011 	for (i = step; i <= change_value; i += step) {
2012 		vadj->value = old_value + (up ? -i : i);
2013 		g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
2014 	}
2015 
2016 	g_signal_handlers_unblock_by_func(vadj, textview_adj_value_changed,
2017 					  textview);
2018 
2019 	vadj->value = last_value;
2020 	g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
2021 
2022 	/* gdk_key_repeat_restore(); */
2023 
2024 	gtk_widget_queue_draw(GTK_WIDGET(text));
2025 }
2026 
textview_smooth_scroll_one_line(TextView * textview,gboolean up)2027 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
2028 {
2029 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
2030 	GtkAdjustment *vadj = text->vadjustment;
2031 	gfloat upper;
2032 	gfloat old_value;
2033 	gfloat last_value;
2034 
2035 	if (!up) {
2036 		upper = vadj->upper - vadj->page_size;
2037 		if (vadj->value < upper) {
2038 			old_value = vadj->value;
2039 			last_value = vadj->value + vadj->step_increment;
2040 			last_value = MIN(last_value, upper);
2041 
2042 			textview_smooth_scroll_do(textview, old_value,
2043 						  last_value,
2044 						  prefs_common.scroll_step);
2045 		}
2046 	} else {
2047 		if (vadj->value > 0.0) {
2048 			old_value = vadj->value;
2049 			last_value = vadj->value - vadj->step_increment;
2050 			last_value = MAX(last_value, 0.0);
2051 
2052 			textview_smooth_scroll_do(textview, old_value,
2053 						  last_value,
2054 						  prefs_common.scroll_step);
2055 		}
2056 	}
2057 }
2058 
textview_smooth_scroll_page(TextView * textview,gboolean up)2059 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
2060 {
2061 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
2062 	GtkAdjustment *vadj = text->vadjustment;
2063 	gfloat upper;
2064 	gfloat page_incr;
2065 	gfloat old_value;
2066 	gfloat last_value;
2067 
2068 	if (prefs_common.scroll_halfpage)
2069 		page_incr = vadj->page_increment / 2;
2070 	else
2071 		page_incr = vadj->page_increment;
2072 
2073 	if (!up) {
2074 		upper = vadj->upper - vadj->page_size;
2075 		if (vadj->value < upper) {
2076 			old_value = vadj->value;
2077 			last_value = vadj->value + page_incr;
2078 			last_value = MIN(last_value, upper);
2079 
2080 			textview_smooth_scroll_do(textview, old_value,
2081 						  last_value,
2082 						  prefs_common.scroll_step);
2083 		} else
2084 			return FALSE;
2085 	} else {
2086 		if (vadj->value > 0.0) {
2087 			old_value = vadj->value;
2088 			last_value = vadj->value - page_incr;
2089 			last_value = MAX(last_value, 0.0);
2090 
2091 			textview_smooth_scroll_do(textview, old_value,
2092 						  last_value,
2093 						  prefs_common.scroll_step);
2094 		} else
2095 			return FALSE;
2096 	}
2097 
2098 	return TRUE;
2099 }
2100 
2101 #define KEY_PRESS_EVENT_STOP() \
2102 	g_signal_stop_emission_by_name(G_OBJECT(widget), "key-press-event");
2103 
textview_key_pressed(GtkWidget * widget,GdkEventKey * event,TextView * textview)2104 static gboolean textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
2105 				     TextView *textview)
2106 {
2107 	SummaryView *summaryview = NULL;
2108 	MessageView *messageview = textview->messageview;
2109 
2110 	if (!event) return FALSE;
2111 	if (messageview->mainwin)
2112 		summaryview = messageview->mainwin->summaryview;
2113 
2114 	switch (event->keyval) {
2115 	case GDK_Tab:
2116 	case GDK_Home:
2117 	case GDK_Left:
2118 	case GDK_Up:
2119 	case GDK_Right:
2120 	case GDK_Down:
2121 	case GDK_Page_Up:
2122 	case GDK_Page_Down:
2123 	case GDK_End:
2124 	case GDK_Control_L:
2125 	case GDK_Control_R:
2126 	case GDK_KP_Tab:
2127 	case GDK_KP_Home:
2128 	case GDK_KP_Left:
2129 	case GDK_KP_Up:
2130 	case GDK_KP_Right:
2131 	case GDK_KP_Down:
2132 	case GDK_KP_Page_Up:
2133 	case GDK_KP_Page_Down:
2134 	case GDK_KP_End:
2135 		break;
2136 	case GDK_space:
2137 	case GDK_KP_Space:
2138 		if (summaryview)
2139 			summary_pass_key_press_event(summaryview, event);
2140 		else
2141 			textview_scroll_page
2142 				(textview,
2143 				 (event->state &
2144 				  (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
2145 		break;
2146 	case GDK_BackSpace:
2147 		textview_scroll_page(textview, TRUE);
2148 		break;
2149 	case GDK_Return:
2150 	case GDK_KP_Enter:
2151 		textview_scroll_one_line
2152 			(textview, (event->state &
2153 				    (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
2154 		break;
2155 	case GDK_Delete:
2156 	case GDK_KP_Delete:
2157 		if (summaryview)
2158 			summary_pass_key_press_event(summaryview, event);
2159 		break;
2160 	case GDK_Escape:
2161 		if (summaryview && textview == messageview->textview)
2162 			gtk_widget_grab_focus(summaryview->treeview);
2163 		else if (messageview->type == MVIEW_MIME &&
2164 			 textview == messageview->mimeview->textview)
2165 			gtk_widget_grab_focus(messageview->mimeview->treeview);
2166 		break;
2167 	case GDK_n:
2168 	case GDK_N:
2169 	case GDK_p:
2170 	case GDK_P:
2171 	case GDK_y:
2172 	case GDK_t:
2173 	case GDK_l:
2174 		if (messageview->type == MVIEW_MIME &&
2175 		    textview == messageview->mimeview->textview) {
2176 			KEY_PRESS_EVENT_STOP();
2177 			mimeview_pass_key_press_event(messageview->mimeview,
2178 						      event);
2179 			break;
2180 		}
2181 		/* fall through */
2182 	default:
2183 		if (summaryview &&
2184 		    event->window != messageview->mainwin->window->window) {
2185 			GdkEventKey tmpev = *event;
2186 
2187 			tmpev.window = messageview->mainwin->window->window;
2188 			KEY_PRESS_EVENT_STOP();
2189 			gtk_widget_event(messageview->mainwin->window,
2190 					 (GdkEvent *)&tmpev);
2191 		}
2192 		break;
2193 	}
2194 
2195 	return FALSE;
2196 }
2197 
textview_get_link_tag_bounds(TextView * textview,GtkTextIter * iter,GtkTextIter * start,GtkTextIter * end)2198 static gboolean textview_get_link_tag_bounds(TextView *textview,
2199 					     GtkTextIter *iter,
2200 					     GtkTextIter *start,
2201 					     GtkTextIter *end)
2202 {
2203 	GSList *tags, *cur;
2204 	gboolean on_link = FALSE;
2205 
2206 	tags = gtk_text_iter_get_tags(iter);
2207 	*start = *end = *iter;
2208 
2209 	for (cur = tags; cur != NULL; cur = cur->next) {
2210 		GtkTextTag *tag = cur->data;
2211 		gchar *tag_name;
2212 
2213 		g_object_get(G_OBJECT(tag), "name", &tag_name, NULL);
2214 		if (tag_name && !strcmp(tag_name, "link")) {
2215 			if (!gtk_text_iter_begins_tag(start, tag))
2216 				gtk_text_iter_backward_to_tag_toggle
2217 					(start, tag);
2218 			if (!gtk_text_iter_ends_tag(end, tag))
2219 				gtk_text_iter_forward_to_tag_toggle(end, tag);
2220 			on_link = TRUE;
2221 			g_free(tag_name);
2222 			break;
2223 		}
2224 		if (tag_name)
2225 			g_free(tag_name);
2226 	}
2227 
2228 	if (tags)
2229 		g_slist_free(tags);
2230 
2231 	return on_link;
2232 }
2233 
textview_get_uri(TextView * textview,GtkTextIter * start,GtkTextIter * end)2234 static RemoteURI *textview_get_uri(TextView *textview, GtkTextIter *start,
2235 				   GtkTextIter *end)
2236 {
2237 	gint start_pos, end_pos;
2238 	GSList *cur;
2239 	RemoteURI *uri = NULL;
2240 
2241 	start_pos = gtk_text_iter_get_offset(start);
2242 	end_pos = gtk_text_iter_get_offset(end);
2243 
2244 	for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2245 		RemoteURI *uri_ = (RemoteURI *)cur->data;
2246 
2247 		if (start_pos == uri_->start && end_pos == uri_->end) {
2248 			debug_print("uri found: (%d, %d): %s\n",
2249 				    start_pos, end_pos, uri_->uri);
2250 			uri = uri_;
2251 			break;
2252 		}
2253 	}
2254 
2255 	return uri;
2256 }
2257 
textview_event_after(GtkWidget * widget,GdkEvent * event,TextView * textview)2258 static gboolean textview_event_after(GtkWidget *widget, GdkEvent *event,
2259 				     TextView *textview)
2260 {
2261 	GdkEventButton *bevent = (GdkEventButton *)event;
2262 	GtkTextBuffer *buffer;
2263 	GtkTextIter iter, start, end;
2264 	gint x, y;
2265 	gboolean on_link;
2266 	RemoteURI *uri;
2267 
2268 	if (event->type != GDK_BUTTON_RELEASE)
2269 		return FALSE;
2270 	if (bevent->button != 1 && bevent->button != 2)
2271 		return FALSE;
2272 
2273 	/* don't process child widget's event */
2274 	if (gtk_text_view_get_window(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT) != bevent->window)
2275 		return FALSE;
2276 
2277 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
2278 
2279 	/* don't follow a link if the user has selected something */
2280 	gtk_text_buffer_get_selection_bounds(buffer, &start, &end);
2281 	if (!gtk_text_iter_equal(&start, &end))
2282 		return FALSE;
2283 
2284 	gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget),
2285 					      GTK_TEXT_WINDOW_TEXT,
2286 					      bevent->x, bevent->y, &x, &y);
2287 	gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &iter, x, y);
2288 	on_link = textview_get_link_tag_bounds(textview, &iter, &start, &end);
2289 	if (!on_link)
2290 		return FALSE;
2291 
2292 	uri = textview_get_uri(textview, &start, &end);
2293 	if (!uri || !uri->uri)
2294 		return FALSE;
2295 
2296 	if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2297 		PrefsAccount *ac = NULL;
2298 		MsgInfo *msginfo = textview->messageview->msginfo;
2299 
2300 		if (msginfo && msginfo->folder)
2301 			ac = account_find_from_item(msginfo->folder);
2302 		if (ac && ac->protocol == A_NNTP)
2303 			ac = NULL;
2304 		compose_new(ac, msginfo ? msginfo->folder : NULL,
2305 			    uri->uri + 7, NULL);
2306 	} else if (uri->uri[0] == '#') {
2307 		/* don't open in-page link */
2308 	} else if (textview_uri_security_check(textview, uri) == TRUE)
2309 		open_uri(uri->uri, prefs_common.uri_cmd);
2310 
2311 	return FALSE;
2312 }
2313 
textview_show_uri(TextView * textview,GtkTextIter * start,GtkTextIter * end)2314 static void textview_show_uri(TextView *textview, GtkTextIter *start,
2315 			      GtkTextIter *end)
2316 {
2317 	RemoteURI *uri;
2318 
2319 	STATUSBAR_POP(textview);
2320 	uri = textview_get_uri(textview, start, end);
2321 	if (uri) {
2322 		STATUSBAR_PUSH(textview, uri->uri);
2323 	}
2324 }
2325 
textview_set_cursor(TextView * textview,GtkTextView * text,gint x,gint y)2326 static void textview_set_cursor(TextView *textview, GtkTextView *text,
2327 				gint x, gint y)
2328 {
2329 	GtkTextBuffer *buffer;
2330 	GtkTextIter iter;
2331 	GtkTextIter start, end;
2332 	GtkTextMark *start_mark, *end_mark;
2333 	gboolean on_link = FALSE;
2334 
2335 	buffer = gtk_text_view_get_buffer(text);
2336 	gtk_text_view_get_iter_at_location(text, &iter, x, y);
2337 	on_link = textview_get_link_tag_bounds(textview, &iter, &start, &end);
2338 
2339 	start_mark = gtk_text_buffer_get_mark(buffer, "hover-link-start");
2340 	end_mark = gtk_text_buffer_get_mark(buffer, "hover-link-end");
2341 	if (start_mark) {
2342 		GtkTextIter prev_start, prev_end;
2343 
2344 		gtk_text_buffer_get_iter_at_mark(buffer, &prev_start,
2345 						 start_mark);
2346 		gtk_text_buffer_get_iter_at_mark(buffer, &prev_end, end_mark);
2347 		if (on_link) {
2348 			if (gtk_text_iter_equal(&prev_start, &start))
2349 				return;
2350 		}
2351 
2352 		gtk_text_buffer_get_iter_at_mark(buffer, &prev_start,
2353 						 start_mark);
2354 		gtk_text_buffer_get_iter_at_mark(buffer, &prev_end, end_mark);
2355 		gtk_text_buffer_remove_tag_by_name(buffer, "hover-link",
2356 						   &prev_start, &prev_end);
2357 		gtk_text_buffer_delete_mark(buffer, start_mark);
2358 		gtk_text_buffer_delete_mark(buffer, end_mark);
2359 	} else {
2360 		if (!on_link)
2361 			return;
2362 	}
2363 
2364 	if (on_link) {
2365 		gtk_text_buffer_create_mark
2366 			(buffer, "hover-link-start", &start, FALSE);
2367 		gtk_text_buffer_create_mark
2368 			(buffer, "hover-link-end", &end, FALSE);
2369 		gtk_text_buffer_apply_tag_by_name
2370 			(buffer, "hover-link", &start, &end);
2371 		gdk_window_set_cursor
2372 			(gtk_text_view_get_window(text, GTK_TEXT_WINDOW_TEXT),
2373 			 hand_cursor);
2374 		textview_show_uri(textview, &start, &end);
2375 	} else {
2376 		gdk_window_set_cursor
2377 			(gtk_text_view_get_window(text, GTK_TEXT_WINDOW_TEXT),
2378 			 regular_cursor);
2379 		STATUSBAR_POP(textview);
2380 	}
2381 }
2382 
textview_motion_notify(GtkWidget * widget,GdkEventMotion * event,TextView * textview)2383 static gboolean textview_motion_notify(GtkWidget *widget,
2384 				       GdkEventMotion *event,
2385 				       TextView *textview)
2386 {
2387 	gint x, y;
2388 
2389 	if (gtk_text_view_get_window(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT) != event->window)
2390 		return FALSE;
2391 	gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget),
2392 					      GTK_TEXT_WINDOW_WIDGET,
2393 					      event->x, event->y, &x, &y);
2394 	textview_set_cursor(textview, GTK_TEXT_VIEW(widget), x, y);
2395 	gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
2396 
2397 	return FALSE;
2398 }
2399 
textview_leave_notify(GtkWidget * widget,GdkEventCrossing * event,TextView * textview)2400 static gboolean textview_leave_notify(GtkWidget *widget,
2401 				      GdkEventCrossing *event,
2402 				      TextView *textview)
2403 {
2404 	textview_set_cursor(textview, GTK_TEXT_VIEW(widget), 0, 0);
2405 
2406 	return FALSE;
2407 }
2408 
textview_visibility_notify(GtkWidget * widget,GdkEventVisibility * event,TextView * textview)2409 static gboolean textview_visibility_notify(GtkWidget *widget,
2410 					   GdkEventVisibility *event,
2411 					   TextView *textview)
2412 {
2413 	gint wx, wy, bx, by;
2414 	GdkWindow *window;
2415 
2416 	window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
2417 					  GTK_TEXT_WINDOW_TEXT);
2418 
2419 	/* check if the event occurred for the text window part */
2420 	if (window != event->window)
2421 		return FALSE;
2422 
2423 	gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
2424 	gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget),
2425 					      GTK_TEXT_WINDOW_WIDGET,
2426 					      wx, wy, &bx, &by);
2427 	textview_set_cursor(textview, GTK_TEXT_VIEW(widget), bx, by);
2428 
2429 	return FALSE;
2430 }
2431 
2432 #define ADD_MENU(label, func)						\
2433 {									\
2434 	menuitem = gtk_menu_item_new_with_mnemonic(label);		\
2435 	g_signal_connect(menuitem, "activate", G_CALLBACK(func), uri);	\
2436 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);		\
2437 	g_object_set_data(G_OBJECT(menuitem), "textview", textview);	\
2438 	gtk_widget_show(menuitem);					\
2439 }
2440 
2441 #define ADD_MENU_SEPARATOR()						\
2442 {									\
2443 	menuitem = gtk_menu_item_new();					\
2444 	gtk_widget_set_sensitive(menuitem, FALSE);			\
2445 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);		\
2446 	gtk_widget_show(menuitem);					\
2447 }
2448 
textview_populate_popup(GtkWidget * widget,GtkMenu * menu,TextView * textview)2449 static void textview_populate_popup(GtkWidget *widget, GtkMenu *menu,
2450 				    TextView *textview)
2451 {
2452 	gint px, py, x, y;
2453 	GtkWidget *separator, *menuitem;
2454 	GtkTextBuffer *buffer;
2455 	GtkTextIter iter, start, end;
2456 	gboolean on_link;
2457 	RemoteURI *uri;
2458 	GdkPixbuf *pixbuf;
2459 	gchar *selected_text;
2460 
2461 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
2462 
2463 	gdk_window_get_pointer(widget->window, &px, &py, NULL);
2464 	gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget),
2465 					      GTK_TEXT_WINDOW_WIDGET,
2466 					      px, py, &x, &y);
2467 	gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &iter, x, y);
2468 	if ((pixbuf = gtk_text_iter_get_pixbuf(&iter)) != NULL) {
2469 		start = end = iter;
2470 		gtk_text_iter_forward_char(&end);
2471 		uri = textview_get_uri(textview, &start, &end);
2472 
2473 		separator = gtk_separator_menu_item_new();
2474 		gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator);
2475 		gtk_widget_show(separator);
2476 
2477 		ADD_MENU(_("Sa_ve this image as..."),
2478 			 textview_popup_menu_activate_image_cb);
2479 	}
2480 
2481 	selected_text = gtkut_text_view_get_selection(GTK_TEXT_VIEW(widget));
2482 
2483 	uri = NULL;
2484 	on_link = textview_get_link_tag_bounds(textview, &iter, &start, &end);
2485 	if (!on_link)
2486 		goto finish;
2487 
2488 	uri = textview_get_uri(textview, &start, &end);
2489 	if (!uri || !uri->uri)
2490 		goto finish;
2491 
2492 	separator = gtk_separator_menu_item_new();
2493 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator);
2494 	gtk_widget_show(separator);
2495 
2496 	if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2497 		ADD_MENU(_("Compose _new message"),
2498 			 textview_popup_menu_activate_open_uri_cb);
2499 		ADD_MENU(_("R_eply to this address"),
2500 			 textview_popup_menu_activate_reply_cb);
2501 		ADD_MENU_SEPARATOR();
2502 		ADD_MENU(_("Add to address _book..."),
2503 			 textview_popup_menu_activate_add_address_cb);
2504 		ADD_MENU(_("Copy this add_ress"),
2505 			 textview_popup_menu_activate_copy_cb);
2506 	} else {
2507 		ADD_MENU(_("_Open with Web browser"),
2508 			 textview_popup_menu_activate_open_uri_cb);
2509 		ADD_MENU(_("Copy this _link"),
2510 			 textview_popup_menu_activate_copy_cb);
2511 	}
2512 
2513 finish:
2514 	syl_plugin_signal_emit("textview-menu-popup", menu,
2515 			       GTK_TEXT_VIEW(widget),
2516 			       uri ? uri->uri : NULL,
2517 			       selected_text,
2518 			       textview->messageview->msginfo);
2519 	g_free(selected_text);
2520 }
2521 
textview_popup_menu_activate_open_uri_cb(GtkMenuItem * menuitem,gpointer data)2522 static void textview_popup_menu_activate_open_uri_cb(GtkMenuItem *menuitem,
2523 						     gpointer data)
2524 {
2525 	RemoteURI *uri = (RemoteURI *)data;
2526 	TextView *textview;
2527 
2528 	g_return_if_fail(uri != NULL);
2529 
2530 	if (!uri->uri)
2531 		return;
2532 
2533 	textview = g_object_get_data(G_OBJECT(menuitem), "textview");
2534 	g_return_if_fail(textview != NULL);
2535 
2536 	if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2537 		PrefsAccount *ac = NULL;
2538 		MsgInfo *msginfo = textview->messageview->msginfo;
2539 
2540 		if (msginfo && msginfo->folder)
2541 			ac = account_find_from_item(msginfo->folder);
2542 		if (ac && ac->protocol == A_NNTP)
2543 			ac = NULL;
2544 		compose_new(ac, msginfo ? msginfo->folder : NULL,
2545 			    uri->uri + 7, NULL);
2546 	} else if (uri->uri[0] == '#') {
2547 		/* don't open in-page link */
2548 	} else if (textview_uri_security_check(textview, uri) == TRUE)
2549 		open_uri(uri->uri, prefs_common.uri_cmd);
2550 }
2551 
textview_popup_menu_activate_reply_cb(GtkMenuItem * menuitem,gpointer data)2552 static void textview_popup_menu_activate_reply_cb(GtkMenuItem *menuitem,
2553 						  gpointer data)
2554 {
2555 	RemoteURI *uri = (RemoteURI *)data;
2556 	TextView *textview;
2557 
2558 	g_return_if_fail(uri != NULL);
2559 
2560 	if (!uri->uri)
2561 		return;
2562 
2563 	textview = g_object_get_data(G_OBJECT(menuitem), "textview");
2564 	g_return_if_fail(textview != NULL);
2565 
2566 	if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2567 		MsgInfo *msginfo = textview->messageview->msginfo;
2568 		ComposeMode mode = COMPOSE_REPLY;
2569 		gchar *text;
2570 		GList *list;
2571 		Compose *compose;
2572 
2573 		g_return_if_fail(msginfo != NULL);
2574 
2575 		if (prefs_common.reply_with_quote)
2576 			mode |= COMPOSE_WITH_QUOTE;
2577 
2578 		text = gtkut_text_view_get_selection
2579 			(GTK_TEXT_VIEW(textview->text));
2580 		if (text && *text == '\0') {
2581 			g_free(text);
2582 			text = NULL;
2583 		}
2584 
2585 		compose_reply(msginfo, msginfo->folder, mode, text);
2586 		list = compose_get_compose_list();
2587 		list = g_list_last(list);
2588 		if (list && list->data) {
2589 			compose = (Compose *)list->data;
2590 			compose_block_modified(compose);
2591 			compose_entry_set(compose, uri->uri + 7,
2592 					  COMPOSE_ENTRY_TO);
2593 			compose_unblock_modified(compose);
2594 		}
2595 		g_free(text);
2596 	}
2597 }
2598 
textview_popup_menu_activate_add_address_cb(GtkMenuItem * menuitem,gpointer data)2599 static void textview_popup_menu_activate_add_address_cb(GtkMenuItem *menuitem,
2600 							gpointer data)
2601 {
2602 	RemoteURI *uri = (RemoteURI *)data;
2603 	gchar *addr;
2604 
2605 	g_return_if_fail(uri != NULL);
2606 
2607 	if (!uri->uri)
2608 		return;
2609 
2610 	if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2611 		addr = g_malloc(strlen(uri->uri + 7) + 1);
2612 		decode_uri(addr, uri->uri + 7);
2613 	} else
2614 		addr = g_strdup(uri->uri);
2615 
2616 	addressbook_add_contact(addr, addr, NULL);
2617 
2618 	g_free(addr);
2619 }
2620 
textview_popup_menu_activate_copy_cb(GtkMenuItem * menuitem,gpointer data)2621 static void textview_popup_menu_activate_copy_cb(GtkMenuItem *menuitem,
2622 						 gpointer data)
2623 {
2624 	RemoteURI *uri = (RemoteURI *)data;
2625 	gchar *uri_string;
2626 	GtkClipboard *clipboard;
2627 
2628 	g_return_if_fail(uri != NULL);
2629 
2630 	if (!uri->uri)
2631 		return;
2632 
2633 	if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2634 		uri_string = g_malloc(strlen(uri->uri + 7) + 1);
2635 		decode_uri(uri_string, uri->uri + 7);
2636 	} else
2637 		uri_string = g_strdup(uri->uri);
2638 
2639 	clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2640 	gtk_clipboard_set_text(clipboard, uri_string, -1);
2641 	clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
2642 	gtk_clipboard_set_text(clipboard, uri_string, -1);
2643 
2644 	g_free(uri_string);
2645 }
2646 
textview_popup_menu_activate_image_cb(GtkMenuItem * menuitem,gpointer data)2647 static void textview_popup_menu_activate_image_cb(GtkMenuItem *menuitem,
2648 						  gpointer data)
2649 {
2650 	RemoteURI *uri = (RemoteURI *)data;
2651 	gchar *src;
2652 	gchar *dest;
2653 	gchar *filename;
2654 
2655 	g_return_if_fail(uri != NULL);
2656 
2657 	if (!uri->uri)
2658 		return;
2659 
2660 	src = g_filename_from_uri(uri->uri, NULL, NULL);
2661 	g_return_if_fail(src != NULL);
2662 
2663 	filename = conv_filename_to_utf8(uri->filename);
2664 	dest = filesel_save_as(filename);
2665 	if (dest) {
2666 		copy_file(src, dest, FALSE);
2667 		g_free(dest);
2668 	}
2669 	g_free(filename);
2670 	g_free(src);
2671 }
2672 
textview_adj_value_changed(GtkAdjustment * adj,gpointer data)2673 static void textview_adj_value_changed(GtkAdjustment *adj, gpointer data)
2674 {
2675 	TextView *textview = (TextView *)data;
2676 	GtkTextBuffer *buffer;
2677 
2678 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2679 	if (gtk_text_buffer_get_selection_bounds(buffer, NULL, NULL))
2680 		return;
2681 	gtk_text_view_place_cursor_onscreen(GTK_TEXT_VIEW(textview->text));
2682 }
2683 
textview_uri_security_check(TextView * textview,RemoteURI * uri)2684 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2685 {
2686 	GtkTextBuffer *buffer;
2687 	GtkTextIter start_iter, end_iter;
2688 	gchar *visible_str;
2689 	gboolean retval = TRUE;
2690 
2691 	if (is_uri_string(uri->uri) == FALSE)
2692 		return TRUE;
2693 
2694 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2695 	gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, uri->start);
2696 	gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, uri->end);
2697 	visible_str = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter,
2698 					       FALSE);
2699 	if (visible_str == NULL)
2700 		return TRUE;
2701 
2702 	if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2703 		gchar *uri_path;
2704 		gchar *visible_uri_path;
2705 
2706 		uri_path = get_uri_path(uri->uri);
2707 		visible_uri_path = get_uri_path(visible_str);
2708 		if (path_cmp(uri_path, visible_uri_path) != 0)
2709 			retval = FALSE;
2710 	}
2711 
2712 	if (retval == FALSE) {
2713 		gchar *msg;
2714 		AlertValue aval;
2715 
2716 		msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2717 					"the apparent URL (%s).\n"
2718 					"\n"
2719 					"Open it anyway?"),
2720 				      uri->uri, visible_str);
2721 		aval = alertpanel_full(_("Fake URL warning"), msg,
2722 				       ALERT_WARNING, G_ALERTDEFAULT, FALSE,
2723 				       GTK_STOCK_YES, GTK_STOCK_NO, NULL);
2724 		g_free(msg);
2725 		if (aval == G_ALERTDEFAULT)
2726 			retval = TRUE;
2727 	}
2728 
2729 	g_free(visible_str);
2730 
2731 	return retval;
2732 }
2733 
textview_uri_list_remove_all(GSList * uri_list)2734 static void textview_uri_list_remove_all(GSList *uri_list)
2735 {
2736 	GSList *cur;
2737 
2738 	for (cur = uri_list; cur != NULL; cur = cur->next) {
2739 		RemoteURI *uri = (RemoteURI *)cur->data;
2740 
2741 		if (uri) {
2742 			g_free(uri->uri);
2743 			g_free(uri->filename);
2744 			g_free(uri);
2745 		}
2746 	}
2747 
2748 	g_slist_free(uri_list);
2749 }
2750 
textview_uri_list_update_offsets(TextView * textview,gint start,gint add)2751 static void textview_uri_list_update_offsets(TextView *textview, gint start, gint add)
2752 {
2753 	GSList *cur;
2754 
2755 	debug_print("textview_uri_list_update_offsets: from %d: add %d\n", start, add);
2756 
2757 	for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2758 		RemoteURI *uri = (RemoteURI *)cur->data;
2759 
2760 		if (uri->start >= start) {
2761 			uri->start += add;
2762 			uri->end += add;
2763 		}
2764 	}
2765 }
2766