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", "e_colors[0],
369 NULL);
370 textview->quote1_tag =
371 gtk_text_buffer_create_tag(buffer, "quote1",
372 "foreground-gdk", "e_colors[1],
373 NULL);
374 textview->quote2_tag =
375 gtk_text_buffer_create_tag(buffer, "quote2",
376 "foreground-gdk", "e_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 "e_colors[0]);
432 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
433 "e_colors[1]);
434 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
435 "e_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", "e_colors[0],
447 NULL);
448 g_object_set(textview->quote1_tag, "foreground-gdk", "e_colors[1],
449 NULL);
450 g_object_set(textview->quote2_tag, "foreground-gdk", "e_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