diff options
| author | Simeon Simeonov | 2018-02-26 11:23:00 +0100 |
|---|---|---|
| committer | Simeon Simeonov | 2018-02-26 11:23:00 +0100 |
| commit | 0b3cbf57875fd692e4ba0b336fefa4bee1ed00dc (patch) | |
| tree | af916a30553c78ce9d4f2d8658656a175c5b6921 /src/textview.c | |
Initial commit for sylpheed 3.7.0
Diffstat (limited to 'src/textview.c')
| -rw-r--r-- | src/textview.c | 2765 |
1 files changed, 2765 insertions, 0 deletions
diff --git a/src/textview.c b/src/textview.c new file mode 100644 index 0000000..e55eea9 --- /dev/null +++ b/src/textview.c | |||
| @@ -0,0 +1,2765 @@ | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | ||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 */ | ||
| 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 | |||
| 1292 | static gchar *make_uri_string(const gchar *bp, const gchar *ep) | ||
| 1293 | { | ||
| 1294 | return g_strndup(bp, ep - bp); | ||
| 1295 | } | ||
| 1296 | |||
| 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 */ | ||
| 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 | |||
| 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 */ | ||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 1677 | void textview_set_all_headers(TextView *textview, gboolean all_headers) | ||
| 1678 | { | ||
| 1679 | textview->show_all_headers = all_headers; | ||
| 1680 | } | ||
| 1681 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | } | ||
