Statistics
| Revision:

root / src / textview.c @ 1709

History | View | Annotate | Download (57.5 kB)

1
/*
2
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3
 * Copyright (C) 1999-2007 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
60
typedef struct _RemoteURI        RemoteURI;
61
62
struct _RemoteURI
63
{
64
        gchar *uri;
65
66
        gchar *filename;
67
68
        guint start;
69
        guint end;
70
};
71
72
static GdkColor quote_colors[3] = {
73
        {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
74
        {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
75
        {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
76
};
77
78
static GdkColor uri_color = {
79
        (gulong)0,
80
        (gushort)0,
81
        (gushort)0,
82
        (gushort)0
83
};
84
85
static GdkColor emphasis_color = {
86
        (gulong)0,
87
        (gushort)0,
88
        (gushort)0,
89
        (gushort)0xcfff
90
};
91
92
static GdkColor error_color = {
93
        (gulong)0,
94
        (gushort)0xefff,
95
        (gushort)0,
96
        (gushort)0
97
};
98
99
#if USE_GPGME
100
static GdkColor good_sig_color = {
101
        (gulong)0,
102
        (gushort)0,
103
        (gushort)0xbfff,
104
        (gushort)0
105
};
106
107
static GdkColor untrusted_sig_color = {
108
        (gulong)0,
109
        (gushort)0xefff,
110
        (gushort)0,
111
        (gushort)0
112
};
113
114
static GdkColor nocheck_sig_color = {
115
        (gulong)0,
116
        (gushort)0,
117
        (gushort)0,
118
        (gushort)0xcfff
119
};
120
121
static GdkColor bad_sig_color = {
122
        (gulong)0,
123
        (gushort)0xefff,
124
        (gushort)0,
125
        (gushort)0
126
};
127
#endif
128
129
#define STATUSBAR_PUSH(textview, str)                                            \
130
{                                                                            \
131
        gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
132
                           textview->messageview->statusbar_cid, str);            \
133
}
134
135
#define STATUSBAR_POP(textview)                                                   \
136
{                                                                           \
137
        gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
138
                          textview->messageview->statusbar_cid);           \
139
}
140
141
static GdkCursor *hand_cursor = NULL;
142
static GdkCursor *regular_cursor = NULL;
143
144
145
static void textview_add_part                (TextView        *textview,
146
                                         MimeInfo        *mimeinfo,
147
                                         FILE                *fp);
148
#if USE_GPGME
149
static void textview_add_sig_part        (TextView        *textview,
150
                                         MimeInfo        *mimeinfo);
151
#endif
152
static void textview_add_parts                (TextView        *textview,
153
                                         MimeInfo        *mimeinfo,
154
                                         FILE                *fp);
155
static void textview_write_body                (TextView        *textview,
156
                                         MimeInfo        *mimeinfo,
157
                                         FILE                *fp,
158
                                         const gchar        *charset);
159
static void textview_show_html                (TextView        *textview,
160
                                         FILE                *fp,
161
                                         CodeConverter        *conv);
162
163
static void textview_write_line                (TextView        *textview,
164
                                         const gchar        *str,
165
                                         CodeConverter        *conv);
166
static void textview_write_link                (TextView        *textview,
167
                                         const gchar        *str,
168
                                         const gchar        *uri,
169
                                         CodeConverter        *conv);
170
171
static GPtrArray *textview_scan_header        (TextView        *textview,
172
                                         FILE                *fp,
173
                                         const gchar        *encoding);
174
static void textview_show_header        (TextView        *textview,
175
                                         GPtrArray        *headers);
176
177
static gboolean textview_key_pressed                (GtkWidget        *widget,
178
                                                 GdkEventKey        *event,
179
                                                 TextView        *textview);
180
static gboolean textview_event_after                (GtkWidget        *widget,
181
                                                 GdkEvent        *event,
182
                                                 TextView        *textview);
183
static gboolean textview_motion_notify                (GtkWidget        *widget,
184
                                                 GdkEventMotion        *event,
185
                                                 TextView        *textview);
186
static gboolean textview_leave_notify                (GtkWidget          *widget,
187
                                                 GdkEventCrossing *event,
188
                                                 TextView          *textview);
189
static gboolean textview_visibility_notify        (GtkWidget        *widget,
190
                                                 GdkEventVisibility *event,
191
                                                 TextView        *textview);
192
193
static void textview_populate_popup                (GtkWidget        *widget,
194
                                                 GtkMenu        *menu,
195
                                                 TextView        *textview);
196
static void textview_popup_menu_activate_open_uri_cb
197
                                                (GtkMenuItem        *menuitem,
198
                                                 gpointer         data);
199
static void textview_popup_menu_activate_add_address_cb
200
                                                (GtkMenuItem        *menuitem,
201
                                                 gpointer         data);
202
static void textview_popup_menu_activate_copy_cb(GtkMenuItem        *menuitem,
203
                                                 gpointer         data);
204
static void textview_popup_menu_activate_image_cb
205
                                                (GtkMenuItem        *menuitem,
206
                                                 gpointer         data);
207
208
static void textview_adj_value_changed                (GtkAdjustment        *adj,
209
                                                 gpointer         data);
210
211
static void textview_smooth_scroll_do                (TextView        *textview,
212
                                                 gfloat                 old_value,
213
                                                 gfloat                 last_value,
214
                                                 gint                 step);
215
static void textview_smooth_scroll_one_line        (TextView        *textview,
216
                                                 gboolean         up);
217
static gboolean textview_smooth_scroll_page        (TextView        *textview,
218
                                                 gboolean         up);
219
220
static gboolean textview_get_link_tag_bounds        (TextView        *textview,
221
                                                 GtkTextIter        *iter,
222
                                                 GtkTextIter        *start,
223
                                                 GtkTextIter        *end);
224
static RemoteURI *textview_get_uri                (TextView        *textview,
225
                                                 GtkTextIter        *start,
226
                                                 GtkTextIter        *end);
227
static void textview_show_uri                        (TextView        *textview,
228
                                                 GtkTextIter        *start,
229
                                                 GtkTextIter        *end);
230
static void textview_set_cursor                        (TextView        *textview,
231
                                                 GtkTextView        *text,
232
                                                 gint                 x,
233
                                                 gint                 y);
234
235
static gboolean textview_uri_security_check        (TextView        *textview,
236
                                                 RemoteURI        *uri);
237
static void textview_uri_list_remove_all        (GSList                *uri_list);
238
239
240
TextView *textview_create(void)
241
{
242
        TextView *textview;
243
        GtkWidget *vbox;
244
        GtkWidget *scrolledwin;
245
        GtkWidget *text;
246
        GtkTextBuffer *buffer;
247
        GtkClipboard *clipboard;
248
249
        debug_print(_("Creating text view...\n"));
250
        textview = g_new0(TextView, 1);
251
252
        scrolledwin = gtk_scrolled_window_new(NULL, NULL);
253
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
254
                                       GTK_POLICY_AUTOMATIC,
255
                                       GTK_POLICY_AUTOMATIC);
256
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
257
                                            GTK_SHADOW_ETCHED_IN);
258
        gtk_widget_set_size_request
259
                (scrolledwin, prefs_common.mainview_width, -1);
260
261
        text = gtk_text_view_new();
262
        gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
263
        gtk_widget_show(text);
264
        gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
265
        gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
266
        gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
267
        gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
268
269
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
270
        clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
271
        gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
272
273
        gtk_widget_ref(scrolledwin);
274
275
        gtk_container_add(GTK_CONTAINER(scrolledwin), text);
276
277
        g_signal_connect(G_OBJECT(text), "key-press-event",
278
                         G_CALLBACK(textview_key_pressed), textview);
279
        g_signal_connect(G_OBJECT(text), "event-after",
280
                         G_CALLBACK(textview_event_after), textview);
281
        g_signal_connect(G_OBJECT(text), "motion-notify-event",
282
                         G_CALLBACK(textview_motion_notify), textview);
283
        g_signal_connect(G_OBJECT(text), "leave-notify-event",
284
                         G_CALLBACK(textview_leave_notify), textview);
285
        g_signal_connect(G_OBJECT(text), "visibility-notify-event",
286
                         G_CALLBACK(textview_visibility_notify), textview);
287
        g_signal_connect(G_OBJECT(text), "populate-popup",
288
                         G_CALLBACK(textview_populate_popup), textview);
289
290
        g_signal_connect(G_OBJECT(GTK_TEXT_VIEW(text)->vadjustment),
291
                         "value-changed",
292
                         G_CALLBACK(textview_adj_value_changed), textview);
293
294
        gtk_widget_show(scrolledwin);
295
296
        vbox = gtk_vbox_new(FALSE, 0);
297
        gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
298
299
        gtk_widget_show(vbox);
300
301
        textview->vbox             = vbox;
302
        textview->scrolledwin      = scrolledwin;
303
        textview->text             = text;
304
        textview->uri_list         = NULL;
305
        textview->body_pos         = 0;
306
        textview->show_all_headers = FALSE;
307
308
        return textview;
309
}
310
311
static void textview_create_tags(TextView *textview)
312
{
313
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
314
        GtkTextBuffer *buffer;
315
        static PangoFontDescription *font_desc;
316
317
        if (!font_desc)
318
                font_desc = gtkut_get_default_font_desc();
319
320
        buffer = gtk_text_view_get_buffer(text);
321
322
        gtk_text_buffer_create_tag(buffer, "header",
323
                                   "pixels-above-lines", 1,
324
                                   "pixels-above-lines-set", TRUE,
325
                                   "pixels-below-lines", 0,
326
                                   "pixels-below-lines-set", TRUE,
327
                                   "font-desc", font_desc,
328
                                   NULL);
329
        gtk_text_buffer_create_tag(buffer, "header_title",
330
                                   "weight", PANGO_WEIGHT_BOLD,
331
                                   NULL);
332
333
        gtk_text_buffer_create_tag(buffer, "mimepart",
334
                                   "pixels-above-lines", 1,
335
                                   "pixels-above-lines-set", TRUE,
336
                                   "pixels-below-lines", 1,
337
                                   "pixels-below-lines-set", TRUE,
338
                                   "font-desc", font_desc,
339
                                   NULL);
340
341
        textview->quote0_tag =
342
                gtk_text_buffer_create_tag(buffer, "quote0",
343
                                           "foreground-gdk", &quote_colors[0],
344
                                           NULL);
345
        textview->quote1_tag =
346
                gtk_text_buffer_create_tag(buffer, "quote1",
347
                                           "foreground-gdk", &quote_colors[1],
348
                                           NULL);
349
        textview->quote2_tag =
350
                gtk_text_buffer_create_tag(buffer, "quote2",
351
                                           "foreground-gdk", &quote_colors[2],
352
                                           NULL);
353
        textview->link_tag =
354
                gtk_text_buffer_create_tag(buffer, "link",
355
                                           "foreground-gdk", &uri_color,
356
                                           NULL);
357
        textview->hover_link_tag =
358
                gtk_text_buffer_create_tag(buffer, "hover-link",
359
                                           "foreground-gdk", &uri_color,
360
                                           "underline", PANGO_UNDERLINE_SINGLE,
361
                                           NULL);
362
363
        gtk_text_buffer_create_tag(buffer, "emphasis",
364
                                   "foreground-gdk", &emphasis_color,
365
                                   NULL);
366
        gtk_text_buffer_create_tag(buffer, "error",
367
                                   "foreground-gdk", &error_color,
368
                                   NULL);
369
#if USE_GPGME
370
        gtk_text_buffer_create_tag(buffer, "good-signature",
371
                                   "foreground-gdk", &good_sig_color,
372
                                   NULL);
373
        gtk_text_buffer_create_tag(buffer, "untrusted-signature",
374
                                   "foreground-gdk", &untrusted_sig_color,
375
                                   NULL);
376
        gtk_text_buffer_create_tag(buffer, "bad-signature",
377
                                   "foreground-gdk", &bad_sig_color,
378
                                   NULL);
379
        gtk_text_buffer_create_tag(buffer, "nocheck-signature",
380
                                   "foreground-gdk", &nocheck_sig_color,
381
                                   NULL);
382
#endif /* USE_GPGME */
383
}
384
385
void textview_init(TextView *textview)
386
{
387
        if (!hand_cursor)
388
                hand_cursor = gdk_cursor_new(GDK_HAND2);
389
        if (!regular_cursor)
390
                regular_cursor = gdk_cursor_new(GDK_XTERM);
391
392
        textview_create_tags(textview);
393
        textview_reflect_prefs(textview);
394
        textview_set_all_headers(textview, FALSE);
395
        textview_set_font(textview, NULL);
396
}
397
398
static void textview_update_message_colors(void)
399
{
400
        GdkColor black = {0, 0, 0, 0};
401
402
        if (prefs_common.enable_color) {
403
                /* grab the quote colors, converting from an int to a
404
                   GdkColor */
405
                gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
406
                                               &quote_colors[0]);
407
                gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
408
                                               &quote_colors[1]);
409
                gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
410
                                               &quote_colors[2]);
411
                gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
412
                                               &uri_color);
413
        } else {
414
                quote_colors[0] = quote_colors[1] = quote_colors[2] = 
415
                        uri_color = emphasis_color = black;
416
        }
417
}
418
419
static void textview_update_tags(TextView *textview)
420
{
421
        g_object_set(textview->quote0_tag, "foreground-gdk", &quote_colors[0],
422
                     NULL);
423
        g_object_set(textview->quote1_tag, "foreground-gdk", &quote_colors[1],
424
                     NULL);
425
        g_object_set(textview->quote2_tag, "foreground-gdk", &quote_colors[2],
426
                     NULL);
427
        g_object_set(textview->link_tag, "foreground-gdk", &uri_color, NULL);
428
        g_object_set(textview->hover_link_tag, "foreground-gdk", &uri_color,
429
                     NULL);
430
}
431
432
void textview_reflect_prefs(TextView *textview)
433
{
434
        textview_update_message_colors();
435
        textview_update_tags(textview);
436
        gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview->text),
437
                                         prefs_common.textview_cursor_visible);
438
}
439
440
static const gchar *textview_get_src_encoding(TextView *textview,
441
                                              MimeInfo *mimeinfo)
442
{
443
        const gchar *charset;
444
445
        if (textview->messageview->forced_charset)
446
                charset = textview->messageview->forced_charset;
447
        else if (!textview->messageview->new_window &&
448
                 prefs_common.force_charset)
449
                charset = prefs_common.force_charset;
450
        else if (mimeinfo->charset)
451
                charset = mimeinfo->charset;
452
        else if (prefs_common.default_encoding)
453
                charset = prefs_common.default_encoding;
454
        else
455
                charset = NULL;
456
457
        return charset;
458
}
459
460
void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
461
                           const gchar *file)
462
{
463
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
464
        GtkTextBuffer *buffer;
465
        GtkTextMark *mark;
466
        GtkTextIter iter;
467
        FILE *fp;
468
        const gchar *charset;
469
        GPtrArray *headers = NULL;
470
471
        buffer = gtk_text_view_get_buffer(text);
472
473
        if ((fp = g_fopen(file, "rb")) == NULL) {
474
                FILE_OP_ERROR(file, "fopen");
475
                return;
476
        }
477
478
        charset = textview_get_src_encoding(textview, mimeinfo);
479
        textview_set_font(textview, charset);
480
        textview_clear(textview);
481
482
        if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) perror("fseek");
483
        headers = textview_scan_header(textview, fp, charset);
484
        if (headers) {
485
                textview_show_header(textview, headers);
486
                procheader_header_array_destroy(headers);
487
488
                gtk_text_buffer_get_end_iter(buffer, &iter);
489
                textview->body_pos = gtk_text_iter_get_offset(&iter);
490
        }
491
492
#if USE_GPGME
493
        if (textview->messageview->msginfo->encinfo &&
494
            textview->messageview->msginfo->encinfo->decryption_failed) {
495
                gtk_text_buffer_get_end_iter(buffer, &iter);
496
                gtk_text_buffer_insert(buffer, &iter, "\n", 1);
497
                gtk_text_buffer_insert_with_tags_by_name
498
                        (buffer, &iter, _("This message is encrypted, but its decryption failed.\n"),
499
                         -1, "error", "mimepart", NULL);
500
        }
501
#endif
502
503
        textview_add_parts(textview, mimeinfo, fp);
504
505
#if USE_GPGME
506
        if (textview->messageview->msginfo->encinfo &&
507
            textview->messageview->msginfo->encinfo->sigstatus)
508
                textview_add_sig_part(textview, NULL);
509
#endif
510
511
        fclose(fp);
512
513
        textview_set_position(textview, 0);
514
        mark = gtk_text_buffer_get_insert(buffer);
515
        gtk_text_view_scroll_mark_onscreen(text, mark);
516
}
517
518
void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
519
{
520
        gchar buf[BUFFSIZE];
521
        const gchar *boundary = NULL;
522
        gint boundary_len = 0;
523
        const gchar *charset;
524
        GPtrArray *headers = NULL;
525
        gboolean is_rfc822_part = FALSE;
526
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
527
        GtkTextBuffer *buffer;
528
        GtkTextIter iter;
529
        GtkTextMark *mark;
530
531
        g_return_if_fail(mimeinfo != NULL);
532
        g_return_if_fail(fp != NULL);
533
534
        if (mimeinfo->mime_type == MIME_MULTIPART) {
535
                textview_clear(textview);
536
                textview_add_parts(textview, mimeinfo, fp);
537
                return;
538
        }
539
540
        if (mimeinfo->parent && mimeinfo->parent->boundary) {
541
                boundary = mimeinfo->parent->boundary;
542
                boundary_len = strlen(boundary);
543
        }
544
545
        charset = textview_get_src_encoding(textview, mimeinfo);
546
547
        if (!boundary && mimeinfo->mime_type == MIME_TEXT) {
548
                if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0)
549
                        perror("fseek");
550
                headers = textview_scan_header(textview, fp, charset);
551
        } else {
552
                if (mimeinfo->mime_type == MIME_TEXT && mimeinfo->parent) {
553
                        glong fpos;
554
                        MimeInfo *parent = mimeinfo->parent;
555
556
                        while (parent->parent) {
557
                                if (parent->main &&
558
                                    parent->main->mime_type ==
559
                                        MIME_MESSAGE_RFC822)
560
                                        break;
561
                                parent = parent->parent;
562
                        }
563
564
                        if ((fpos = ftell(fp)) < 0)
565
                                perror("ftell");
566
                        else if (fseek(fp, parent->fpos, SEEK_SET) < 0)
567
                                perror("fseek");
568
                        else {
569
                                headers = textview_scan_header
570
                                        (textview, fp, charset);
571
                                if (fseek(fp, fpos, SEEK_SET) < 0)
572
                                        perror("fseek");
573
                        }
574
                }
575
                /* skip MIME part headers */
576
                while (fgets(buf, sizeof(buf), fp) != NULL)
577
                        if (buf[0] == '\r' || buf[0] == '\n') break;
578
        }
579
580
        /* display attached RFC822 single text message */
581
        if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
582
                if (headers) procheader_header_array_destroy(headers);
583
                if (!mimeinfo->sub) {
584
                        textview_clear(textview);
585
                        return;
586
                }
587
                headers = textview_scan_header(textview, fp, charset);
588
                mimeinfo = mimeinfo->sub;
589
                is_rfc822_part = TRUE;
590
        }
591
592
        textview_set_font(textview, charset);
593
        textview_clear(textview);
594
595
        buffer = gtk_text_view_get_buffer(text);
596
597
        if (headers) {
598
                textview_show_header(textview, headers);
599
                procheader_header_array_destroy(headers);
600
601
                gtk_text_buffer_get_end_iter(buffer, &iter);
602
                textview->body_pos = gtk_text_iter_get_offset(&iter);
603
                if (!mimeinfo->main)
604
                        gtk_text_buffer_insert(buffer, &iter, "\n", 1);
605
        }
606
607
        if (mimeinfo->mime_type == MIME_MULTIPART || is_rfc822_part)
608
                textview_add_parts(textview, mimeinfo, fp);
609
        else
610
                textview_write_body(textview, mimeinfo, fp, charset);
611
612
        textview_set_position(textview, 0);
613
        mark = gtk_text_buffer_get_insert(buffer);
614
        gtk_text_view_scroll_mark_onscreen(text, mark);
615
}
616
617
static void textview_add_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
618
{
619
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
620
        GtkTextBuffer *buffer;
621
        GtkTextIter iter;
622
        gchar buf[BUFFSIZE];
623
        const gchar *boundary = NULL;
624
        gint boundary_len = 0;
625
        const gchar *charset;
626
        GPtrArray *headers = NULL;
627
628
        g_return_if_fail(mimeinfo != NULL);
629
        g_return_if_fail(fp != NULL);
630
631
        buffer = gtk_text_view_get_buffer(text);
632
        gtk_text_buffer_get_end_iter(buffer, &iter);
633
634
        if (mimeinfo->mime_type == MIME_MULTIPART) return;
635
636
        if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) {
637
                perror("fseek");
638
                return;
639
        }
640
641
        if (mimeinfo->parent && mimeinfo->parent->boundary) {
642
                boundary = mimeinfo->parent->boundary;
643
                boundary_len = strlen(boundary);
644
        }
645
646
        while (fgets(buf, sizeof(buf), fp) != NULL)
647
                if (buf[0] == '\r' || buf[0] == '\n') break;
648
649
        charset = textview_get_src_encoding(textview, mimeinfo);
650
651
        if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
652
                headers = textview_scan_header(textview, fp, charset);
653
                if (headers) {
654
                        gtk_text_buffer_insert(buffer, &iter, "\n", 1);
655
                        textview_show_header(textview, headers);
656
                        procheader_header_array_destroy(headers);
657
                }
658
                return;
659
        }
660
661
#if USE_GPGME
662
        if (mimeinfo->parent && mimeinfo->sigstatus) {
663
                textview_add_sig_part(textview, mimeinfo);
664
                return;
665
        }
666
#endif
667
668
        if (mimeinfo->filename || mimeinfo->name)
669
                g_snprintf(buf, sizeof(buf), "\n[%s  %s (%s)]\n",
670
                           mimeinfo->filename ? mimeinfo->filename :
671
                           mimeinfo->name,
672
                           mimeinfo->content_type,
673
                           to_human_readable(mimeinfo->content_size));
674
        else
675
                g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n",
676
                           mimeinfo->content_type,
677
                           to_human_readable(mimeinfo->content_size));
678
679
        if (mimeinfo->mime_type != MIME_TEXT &&
680
            mimeinfo->mime_type != MIME_TEXT_HTML) {
681
                gtk_text_buffer_insert_with_tags_by_name
682
                        (buffer, &iter, buf, -1, "mimepart", NULL);
683
                if (mimeinfo->mime_type == MIME_IMAGE &&
684
                    prefs_common.inline_image) {
685
                        GdkPixbuf *pixbuf;
686
                        GError *error = NULL;
687
                        gchar *filename;
688
                        RemoteURI *uri;
689
                        gchar *uri_str;
690
691
                        filename = procmime_get_tmp_file_name(mimeinfo);
692
                        if (procmime_get_part_fp(filename, fp, mimeinfo) < 0) {
693
                                g_warning("Can't get the image file.");
694
                                g_free(filename);
695
                                return;
696
                        }
697
698
                        pixbuf = gdk_pixbuf_new_from_file(filename, &error);
699
                        if (error != NULL) {
700
                                g_warning("%s\n", error->message);
701
                                g_error_free(error);
702
                        }
703
                        if (!pixbuf) {
704
                                g_warning("Can't load the image.");
705
                                g_free(filename);
706
                                return;
707
                        }
708
709
                        if (prefs_common.resize_image) {
710
                                GdkPixbuf *scaled;
711
712
                                scaled = imageview_get_resized_pixbuf
713
                                        (pixbuf, textview->text, 8);
714
                                g_object_unref(pixbuf);
715
                                pixbuf = scaled;
716
                        }
717
718
                        uri_str = g_filename_to_uri(filename, NULL, NULL);
719
                        if (uri_str) {
720
                                uri = g_new(RemoteURI, 1);
721
                                uri->uri = uri_str;
722
                                uri->filename =
723
                                        procmime_get_part_file_name(mimeinfo);
724
                                uri->start = gtk_text_iter_get_offset(&iter);
725
                                uri->end = uri->start + 1;
726
                                textview->uri_list =
727
                                        g_slist_append(textview->uri_list, uri);
728
                        }
729
                        gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf);
730
                        gtk_text_buffer_insert(buffer, &iter, "\n", 1);
731
732
                        g_object_unref(pixbuf);
733
                        g_free(filename);
734
                }
735
        } else {
736
                if (!mimeinfo->main &&
737
                    mimeinfo->parent &&
738
                    mimeinfo->parent->children != mimeinfo)
739
                        gtk_text_buffer_insert_with_tags_by_name
740
                                (buffer, &iter, buf, -1, "mimepart", NULL);
741
                else
742
                        gtk_text_buffer_insert(buffer, &iter, "\n", 1);
743
                textview_write_body(textview, mimeinfo, fp, charset);
744
        }
745
}
746
747
#if USE_GPGME
748
static void textview_add_sig_part(TextView *textview, MimeInfo *mimeinfo)
749
{
750
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
751
        GtkTextBuffer *buffer;
752
        GtkTextIter iter;
753
        gchar buf[BUFFSIZE];
754
        const gchar *color;
755
        const gchar *sigstatus;
756
        const gchar *sigstatus_full;
757
        const gchar *type;
758
759
        if (mimeinfo) {
760
                sigstatus = mimeinfo->sigstatus;
761
                sigstatus_full = mimeinfo->sigstatus_full;
762
                type = mimeinfo->content_type;
763
        } else if (textview->messageview->msginfo->encinfo) {
764
                sigstatus = textview->messageview->msginfo->encinfo->sigstatus;
765
                sigstatus_full =
766
                        textview->messageview->msginfo->encinfo->sigstatus_full;
767
                type = "signature";
768
        } else
769
                return;
770
771
        if (!sigstatus)
772
                return;
773
774
        g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n", type, sigstatus);
775
776
        if (!strcmp(sigstatus, _("Good signature")))
777
                color = "good-signature";
778
        else if (!strcmp(sigstatus, _("Valid signature (untrusted key)")))
779
                color = "untrusted-signature";
780
        else if (!strcmp(sigstatus, _("BAD signature")))
781
                color = "bad-signature";
782
        else
783
                color = "nocheck-signature";
784
785
        buffer = gtk_text_view_get_buffer(text);
786
        gtk_text_buffer_get_end_iter(buffer, &iter);
787
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, buf, -1,
788
                                                 color, "mimepart", NULL);
789
        if (sigstatus_full)
790
                gtk_text_buffer_insert_with_tags_by_name
791
                        (buffer, &iter, sigstatus_full, -1, "mimepart", NULL);
792
}
793
#endif
794
795
static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
796
{
797
        gint level;
798
799
        g_return_if_fail(mimeinfo != NULL);
800
        g_return_if_fail(fp != NULL);
801
802
        level = mimeinfo->level;
803
804
        for (;;) {
805
                textview_add_part(textview, mimeinfo, fp);
806
                if (mimeinfo->parent && mimeinfo->parent->content_type &&
807
                    !g_ascii_strcasecmp(mimeinfo->parent->content_type,
808
                                        "multipart/alternative"))
809
                        mimeinfo = mimeinfo->parent->next;
810
                else
811
                        mimeinfo = procmime_mimeinfo_next(mimeinfo);
812
                if (!mimeinfo || mimeinfo->level <= level)
813
                        break;
814
        }
815
}
816
817
static void textview_write_error(TextView *textview, const gchar *msg)
818
{
819
        GtkTextBuffer *buffer;
820
        GtkTextIter iter;
821
822
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
823
        gtk_text_buffer_get_end_iter(buffer, &iter);
824
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, msg, -1,
825
                                                 "error", NULL);
826
}
827
828
void textview_show_error(TextView *textview)
829
{
830
        textview_set_font(textview, NULL);
831
        textview_clear(textview);
832
        textview_write_error(textview, _("This message can't be displayed.\n"));
833
}
834
835
static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
836
                                FILE *fp, const gchar *charset)
837
{
838
        FILE *tmpfp;
839
        gchar buf[BUFFSIZE];
840
        CodeConverter *conv;
841
842
        conv = conv_code_converter_new(charset, NULL);
843
844
        tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
845
        if (tmpfp) {
846
                if (mimeinfo->mime_type == MIME_TEXT_HTML &&
847
                    prefs_common.render_html)
848
                        textview_show_html(textview, tmpfp, conv);
849
                else
850
                        while (fgets(buf, sizeof(buf), tmpfp) != NULL)
851
                                textview_write_line(textview, buf, conv);
852
                fclose(tmpfp);
853
        } else {
854
                textview_write_error
855
                        (textview,
856
                         _("The body text couldn't be displayed because "
857
                           "writing to temporary file failed.\n"));
858
        }
859
860
        conv_code_converter_destroy(conv);
861
}
862
863
static void textview_show_html(TextView *textview, FILE *fp,
864
                               CodeConverter *conv)
865
{
866
        HTMLParser *parser;
867
        const gchar *str;
868
869
        parser = html_parser_new(fp, conv);
870
        g_return_if_fail(parser != NULL);
871
872
        while ((str = html_parse(parser)) != NULL) {
873
                if (parser->href != NULL)
874
                        textview_write_link(textview, str, parser->href, NULL);
875
                else
876
                        textview_write_line(textview, str, NULL);
877
        }
878
        textview_write_line(textview, "\n", NULL);
879
880
        html_parser_destroy(parser);
881
}
882
883
/* get_uri_part() - retrieves a URI starting from scanpos.
884
                    Returns TRUE if succesful */
885
static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
886
                             const gchar **bp, const gchar **ep)
887
{
888
        const gchar *ep_;
889
890
        g_return_val_if_fail(start != NULL, FALSE);
891
        g_return_val_if_fail(scanpos != NULL, FALSE);
892
        g_return_val_if_fail(bp != NULL, FALSE);
893
        g_return_val_if_fail(ep != NULL, FALSE);
894
895
        *bp = scanpos;
896
897
        /* find end point of URI */
898
        for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
899
                if (!g_ascii_isgraph(*ep_) ||
900
                    !isascii(*(const guchar *)ep_) ||
901
                    strchr("()<>{}[]\"", *ep_))
902
                        break;
903
        }
904
905
        /* no punctuation at end of string */
906
907
        /* FIXME: this stripping of trailing punctuations may bite with other URIs.
908
         * should pass some URI type to this function and decide on that whether
909
         * to perform punctuation stripping */
910
911
#define IS_REAL_PUNCT(ch)        (g_ascii_ispunct(ch) && !strchr("/?=", ch)) 
912
913
        for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
914
                ;
915
916
#undef IS_REAL_PUNCT
917
918
        *ep = ep_;
919
920
        return TRUE;                
921
}
922
923
static gchar *make_uri_string(const gchar *bp, const gchar *ep)
924
{
925
        return g_strndup(bp, ep - bp);
926
}
927
928
static gchar *make_http_uri_string(const gchar *bp, const gchar *ep)
929
{
930
        gchar *tmp;
931
        gchar *result;
932
933
        tmp = g_strndup(bp, ep - bp);
934
        result = g_strconcat("http://", tmp, NULL);
935
        g_free(tmp);
936
937
        return result;
938
}
939
940
/* valid mail address characters */
941
#define IS_RFC822_CHAR(ch) \
942
        (isascii(ch) && \
943
         (ch) > 32   && \
944
         (ch) != 127 && \
945
         !g_ascii_isspace(ch) && \
946
         !strchr("(),;<>\"", (ch)))
947
948
/* alphabet and number within 7bit ASCII */
949
#define IS_ASCII_ALNUM(ch)        (isascii(ch) && g_ascii_isalnum(ch))
950
951
/* get_email_part() - retrieves an email address. Returns TRUE if succesful */
952
static gboolean get_email_part(const gchar *start, const gchar *scanpos,
953
                               const gchar **bp, const gchar **ep)
954
{
955
        /* more complex than the uri part because we need to scan back and forward starting from
956
         * the scan position. */
957
        gboolean result = FALSE;
958
        const gchar *bp_;
959
        const gchar *ep_;
960
961
        g_return_val_if_fail(start != NULL, FALSE);
962
        g_return_val_if_fail(scanpos != NULL, FALSE);
963
        g_return_val_if_fail(bp != NULL, FALSE);
964
        g_return_val_if_fail(ep != NULL, FALSE);
965
966
        /* scan start of address */
967
        for (bp_ = scanpos - 1;
968
             bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
969
                ;
970
971
        /* TODO: should start with an alnum? */
972
        bp_++;
973
        for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
974
                ;
975
976
        if (bp_ != scanpos) {
977
                /* scan end of address */
978
                for (ep_ = scanpos + 1;
979
                     *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
980
                        ;
981
982
                /* TODO: really should terminate with an alnum? */
983
                for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
984
                     --ep_)
985
                        ;
986
                ep_++;
987
988
                if (ep_ > scanpos + 1) {
989
                        *ep = ep_;
990
                        *bp = bp_;
991
                        result = TRUE;
992
                }
993
        }
994
995
        return result;
996
}
997
998
#undef IS_ASCII_ALNUM
999
#undef IS_RFC822_CHAR
1000
1001
static gchar *make_email_string(const gchar *bp, const gchar *ep)
1002
{
1003
        /* returns a mailto: URI; mailto: is also used to detect the
1004
         * uri type later on in the button_pressed signal handler */
1005
        gchar *tmp, *enc;
1006
        gchar *result;
1007
1008
        tmp = g_strndup(bp, ep - bp);
1009
        enc = uriencode_for_mailto(tmp);
1010
        result = g_strconcat("mailto:", enc, NULL);
1011
        g_free(enc);
1012
        g_free(tmp);
1013
1014
        return result;
1015
}
1016
1017
#define ADD_TXT_POS(bp_, ep_, pti_) \
1018
        if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1019
                last = last->next; \
1020
                last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1021
                last->next = NULL; \
1022
        } else { \
1023
                g_warning("alloc error scanning URIs\n"); \
1024
                gtk_text_buffer_insert_with_tags_by_name \
1025
                        (buffer, &iter, linebuf, -1, fg_tag, NULL); \
1026
                return; \
1027
        }
1028
1029
/* textview_make_clickable_parts() - colorizes clickable parts */
1030
static void textview_make_clickable_parts(TextView *textview,
1031
                                          const gchar *fg_tag,
1032
                                          const gchar *uri_tag,
1033
                                          const gchar *linebuf)
1034
{
1035
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1036
        GtkTextBuffer *buffer;
1037
        GtkTextIter iter;
1038
1039
        /* parse table - in order of priority */
1040
        struct table {
1041
                const gchar *needle; /* token */
1042
1043
                /* token search function */
1044
                gchar    *(*search)        (const gchar *haystack,
1045
                                         const gchar *needle);
1046
                /* part parsing function */
1047
                gboolean  (*parse)        (const gchar *start,
1048
                                         const gchar *scanpos,
1049
                                         const gchar **bp_,
1050
                                         const gchar **ep_);
1051
                /* part to URI function */
1052
                gchar    *(*build_uri)        (const gchar *bp,
1053
                                         const gchar *ep);
1054
        };
1055
1056
        static struct table parser[] = {
1057
                {"http://",  strcasestr, get_uri_part,   make_uri_string},
1058
                {"https://", strcasestr, get_uri_part,   make_uri_string},
1059
                {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
1060
                {"www.",     strcasestr, get_uri_part,   make_http_uri_string},
1061
                {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
1062
                {"@",        strcasestr, get_email_part, make_email_string}
1063
        };
1064
        const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1065
1066
        /* flags for search optimization */
1067
        gboolean do_search[] = {TRUE, TRUE, TRUE, TRUE, TRUE, TRUE};
1068
1069
        gint  n;
1070
        const gchar *walk, *bp, *ep;
1071
1072
        struct txtpos {
1073
                const gchar        *bp, *ep;        /* text position */
1074
                gint                 pti;                /* index in parse table */
1075
                struct txtpos        *next;                /* next */
1076
        } head = {NULL, NULL, 0,  NULL}, *last = &head;
1077
1078
        buffer = gtk_text_view_get_buffer(text);
1079
        gtk_text_buffer_get_end_iter(buffer, &iter);
1080
1081
        /* parse for clickable parts, and build a list of begin and
1082
           end positions  */
1083
        for (walk = linebuf, n = 0;;) {
1084
                gint last_index = PARSE_ELEMS;
1085
                const gchar *scanpos = NULL;
1086
1087
                /* FIXME: this looks phony. scanning for anything in the
1088
                   parse table */
1089
                for (n = 0; n < PARSE_ELEMS; n++) {
1090
                        const gchar *tmp;
1091
1092
                        if (do_search[n]) {
1093
                                tmp = parser[n].search(walk, parser[n].needle);
1094
                                if (tmp) {
1095
                                        if (scanpos == NULL || tmp < scanpos) {
1096
                                                scanpos = tmp;
1097
                                                last_index = n;
1098
                                        }
1099
                                } else
1100
                                        do_search[n] = FALSE;
1101
                        }
1102
                }
1103
1104
                if (scanpos) {
1105
                        /* check if URI can be parsed */
1106
                        if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1107
                            && (ep - bp - 1) > strlen(parser[last_index].needle)) {
1108
                                        ADD_TXT_POS(bp, ep, last_index);
1109
                                        walk = ep;
1110
                        } else
1111
                                walk = scanpos +
1112
                                        strlen(parser[last_index].needle);
1113
                } else
1114
                        break;
1115
        }
1116
1117
        /* colorize this line */
1118
        if (head.next) {
1119
                const gchar *normal_text = linebuf;
1120
1121
                /* insert URIs */
1122
                for (last = head.next; last != NULL;
1123
                     normal_text = last->ep, last = last->next) {
1124
                        RemoteURI *uri;
1125
1126
                        uri = g_new(RemoteURI, 1);
1127
                        if (last->bp - normal_text > 0)
1128
                                gtk_text_buffer_insert_with_tags_by_name
1129
                                        (buffer, &iter,
1130
                                         normal_text,
1131
                                         last->bp - normal_text,
1132
                                         fg_tag, NULL);
1133
                        uri->uri = parser[last->pti].build_uri(last->bp,
1134
                                                               last->ep);
1135
                        uri->filename = NULL;
1136
                        uri->start = gtk_text_iter_get_offset(&iter);
1137
                        gtk_text_buffer_insert_with_tags_by_name
1138
                                (buffer, &iter, last->bp, last->ep - last->bp,
1139
                                 uri_tag, fg_tag, NULL);
1140
                        uri->end = gtk_text_iter_get_offset(&iter);
1141
                        textview->uri_list =
1142
                                g_slist_append(textview->uri_list, uri);
1143
                }
1144
1145
                if (*normal_text)
1146
                        gtkut_text_buffer_insert_with_tag_by_name
1147
                                (buffer, &iter, normal_text, -1, fg_tag);
1148
        } else {
1149
                gtkut_text_buffer_insert_with_tag_by_name
1150
                        (buffer, &iter, linebuf, -1, fg_tag);
1151
        }
1152
}
1153
1154
#undef ADD_TXT_POS
1155
1156
static void textview_write_line(TextView *textview, const gchar *str,
1157
                                CodeConverter *conv)
1158
{
1159
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1160
        GtkTextBuffer *buffer;
1161
        GtkTextIter iter;
1162
        gchar *buf;
1163
        gchar *fg_color = NULL;
1164
        gint quotelevel = -1;
1165
        gchar quote_tag_str[10];
1166
1167
        buffer = gtk_text_view_get_buffer(text);
1168
        gtk_text_buffer_get_end_iter(buffer, &iter);
1169
1170
        if (conv) {
1171
                buf = conv_convert(conv, str);
1172
                if (!buf)
1173
                        buf = conv_utf8todisp(str, NULL);
1174
        } else
1175
                buf = g_strdup(str);
1176
1177
        strcrchomp(buf);
1178
        //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1179
1180
        /* change color of quotation
1181
           >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1182
           Up to 3 levels of quotations are detected, and each
1183
           level is colored using a different color. */
1184
        if (prefs_common.enable_color && strchr(buf, '>')) {
1185
                quotelevel = get_quote_level(buf);
1186
1187
                /* set up the correct foreground color */
1188
                if (quotelevel > 2) {
1189
                        /* recycle colors */
1190
                        if (prefs_common.recycle_quote_colors)
1191
                                quotelevel %= 3;
1192
                        else
1193
                                quotelevel = 2;
1194
                }
1195
        }
1196
1197
        if (quotelevel != -1) {
1198
                g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1199
                           "quote%d", quotelevel);
1200
                fg_color = quote_tag_str;
1201
        }
1202
1203
        if (prefs_common.enable_color)
1204
                textview_make_clickable_parts(textview, fg_color, "link", buf);
1205
        else
1206
                textview_make_clickable_parts(textview, fg_color, NULL, buf);
1207
1208
        g_free(buf);
1209
}
1210
1211
static void textview_write_link(TextView *textview, const gchar *str,
1212
                                const gchar *uri, CodeConverter *conv)
1213
{
1214
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1215
        GtkTextBuffer *buffer;
1216
        GtkTextIter iter;
1217
        gchar *buf;
1218
        gchar *bufp;
1219
        RemoteURI *r_uri;
1220
1221
        if (!str || *str == '\0')
1222
                return;
1223
        if (!uri)
1224
                return;
1225
1226
        buffer = gtk_text_view_get_buffer(text);
1227
        gtk_text_buffer_get_end_iter(buffer, &iter);
1228
1229
        if (conv) {
1230
                buf = conv_convert(conv, str);
1231
                if (!buf)
1232
                        buf = conv_utf8todisp(str, NULL);
1233
        } else
1234
                buf = g_strdup(str);
1235
1236
        if (g_utf8_validate(buf, -1, NULL) == FALSE) {
1237
                g_free(buf);
1238
                return;
1239
        }
1240
1241
        strcrchomp(buf);
1242
1243
        for (bufp = buf; *bufp != '\0'; bufp = g_utf8_next_char(bufp)) {
1244
                gunichar ch;
1245
1246
                ch = g_utf8_get_char(bufp);
1247
                if (!g_unichar_isspace(ch))
1248
                        break;
1249
        }
1250
        if (bufp > buf)
1251
                gtk_text_buffer_insert(buffer, &iter, buf, bufp - buf);
1252
1253
        r_uri = g_new(RemoteURI, 1);
1254
        r_uri->uri = g_strstrip(g_strdup(uri));
1255
        r_uri->filename = NULL;
1256
        r_uri->start = gtk_text_iter_get_offset(&iter);
1257
        gtk_text_buffer_insert_with_tags_by_name
1258
                (buffer, &iter, bufp, -1, "link", NULL);
1259
        r_uri->end = gtk_text_iter_get_offset(&iter);
1260
        textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1261
1262
        g_free(buf);
1263
}
1264
1265
void textview_clear(TextView *textview)
1266
{
1267
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1268
        GtkTextBuffer *buffer;
1269
1270
        buffer = gtk_text_view_get_buffer(text);
1271
        gtk_text_buffer_set_text(buffer, "", -1);
1272
1273
        /* workaround for the assertion failure in
1274
           gtk_text_view_validate_onscreen() */
1275
        text->vadjustment->value = 0.0;
1276
1277
        STATUSBAR_POP(textview);
1278
        textview_uri_list_remove_all(textview->uri_list);
1279
        textview->uri_list = NULL;
1280
1281
        textview->body_pos = 0;
1282
}
1283
1284
void textview_destroy(TextView *textview)
1285
{
1286
        GtkTextBuffer *buffer;
1287
        GtkClipboard *clipboard;
1288
1289
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1290
        clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1291
        gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
1292
1293
        textview_uri_list_remove_all(textview->uri_list);
1294
        textview->uri_list = NULL;
1295
        g_free(textview);
1296
}
1297
1298
void textview_set_all_headers(TextView *textview, gboolean all_headers)
1299
{
1300
        textview->show_all_headers = all_headers;
1301
}
1302
1303
void textview_set_font(TextView *textview, const gchar *codeset)
1304
{
1305
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1306
1307
        if (prefs_common.textfont) {
1308
                PangoFontDescription *font_desc;
1309
                font_desc = pango_font_description_from_string
1310
                        (prefs_common.textfont);
1311
                if (font_desc) {
1312
                        gtk_widget_modify_font(textview->text, font_desc);
1313
                        pango_font_description_free(font_desc);
1314
                }
1315
        }
1316
1317
        gtk_text_view_set_pixels_above_lines(text, prefs_common.line_space / 2);
1318
        gtk_text_view_set_pixels_below_lines(text, prefs_common.line_space / 2);
1319
}
1320
1321
void textview_set_position(TextView *textview, gint pos)
1322
{
1323
        GtkTextBuffer *buffer;
1324
        GtkTextIter iter;
1325
1326
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1327
        gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1328
        gtk_text_buffer_place_cursor(buffer, &iter);
1329
}
1330
1331
static GPtrArray *textview_scan_header(TextView *textview, FILE *fp,
1332
                                       const gchar *encoding)
1333
{
1334
        g_return_val_if_fail(fp != NULL, NULL);
1335
1336
        if (textview->show_all_headers)
1337
                return procheader_get_header_array_asis(fp, encoding);
1338
1339
        if (!prefs_common.display_header) {
1340
                gchar buf[BUFFSIZE];
1341
1342
                while (fgets(buf, sizeof(buf), fp) != NULL)
1343
                        if (buf[0] == '\r' || buf[0] == '\n') break;
1344
                return NULL;
1345
        }
1346
1347
        return procheader_get_header_array_for_display(fp, encoding);
1348
}
1349
1350
static void textview_show_header(TextView *textview, GPtrArray *headers)
1351
{
1352
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1353
        GtkTextBuffer *buffer;
1354
        GtkTextIter iter;
1355
        Header *header;
1356
        gint i;
1357
1358
        g_return_if_fail(headers != NULL);
1359
1360
        buffer = gtk_text_view_get_buffer(text);
1361
        gtk_text_buffer_get_end_iter(buffer, &iter);
1362
1363
        for (i = 0; i < headers->len; i++) {
1364
                header = g_ptr_array_index(headers, i);
1365
                g_return_if_fail(header->name != NULL);
1366
1367
                gtk_text_buffer_insert_with_tags_by_name
1368
                        (buffer, &iter, header->name, -1,
1369
                         "header_title", "header", NULL);
1370
                gtk_text_buffer_insert_with_tags_by_name
1371
                        (buffer, &iter, ":", 1,
1372
                         "header_title", "header", NULL);
1373
1374
                if (!g_ascii_strcasecmp(header->name, "Subject") ||
1375
                    !g_ascii_strcasecmp(header->name, "From")    ||
1376
                    !g_ascii_strcasecmp(header->name, "To")      ||
1377
                    !g_ascii_strcasecmp(header->name, "Cc"))
1378
                        unfold_line(header->body);
1379
1380
                if (prefs_common.enable_color &&
1381
                    (!strncmp(header->name, "X-Mailer", 8) ||
1382
                     !strncmp(header->name, "X-Newsreader", 12)) &&
1383
                    strstr(header->body, "Sylpheed") != NULL) {
1384
                        gtk_text_buffer_insert_with_tags_by_name
1385
                                (buffer, &iter, header->body, -1,
1386
                                 "header", "emphasis", NULL);
1387
                } else if (prefs_common.enable_color) {
1388
                        textview_make_clickable_parts
1389
                                (textview, "header", "link", header->body);
1390
                } else {
1391
                        textview_make_clickable_parts
1392
                                (textview, "header", NULL, header->body);
1393
                }
1394
                gtk_text_buffer_get_end_iter(buffer, &iter);
1395
                gtk_text_buffer_insert_with_tags_by_name
1396
                        (buffer, &iter, "\n", 1, "header", NULL);
1397
        }
1398
}
1399
1400
gboolean textview_search_string(TextView *textview, const gchar *str,
1401
                                gboolean case_sens)
1402
{
1403
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1404
        GtkTextBuffer *buffer;
1405
        GtkTextIter iter, match_pos;
1406
        GtkTextMark *mark;
1407
        gint len;
1408
1409
        g_return_val_if_fail(str != NULL, FALSE);
1410
1411
        buffer = gtk_text_view_get_buffer(text);
1412
1413
        len = g_utf8_strlen(str, -1);
1414
        g_return_val_if_fail(len >= 0, FALSE);
1415
1416
        gtk_text_buffer_get_selection_bounds(buffer, NULL, &iter);
1417
1418
        if (gtkut_text_buffer_find(buffer, &iter, str, case_sens,
1419
                                   &match_pos)) {
1420
                GtkTextIter end = match_pos;
1421
1422
                gtk_text_iter_forward_chars(&end, len);
1423
                /* place "insert" at the last character */
1424
                gtk_text_buffer_select_range(buffer, &end, &match_pos);
1425
                mark = gtk_text_buffer_get_insert(buffer);
1426
                gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1427
                return TRUE;
1428
        }
1429
1430
        return FALSE;
1431
}
1432
1433
gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1434
                                         gboolean case_sens)
1435
{
1436
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1437
        GtkTextBuffer *buffer;
1438
        GtkTextIter iter, match_pos;
1439
        GtkTextMark *mark;
1440
        gint len;
1441
1442
        g_return_val_if_fail(str != NULL, FALSE);
1443
1444
        buffer = gtk_text_view_get_buffer(text);
1445
1446
        len = g_utf8_strlen(str, -1);
1447
        g_return_val_if_fail(len >= 0, FALSE);
1448
1449
        gtk_text_buffer_get_selection_bounds(buffer, &iter, NULL);
1450
1451
        if (gtkut_text_buffer_find_backward(buffer, &iter, str, case_sens,
1452
                                            &match_pos)) {
1453
                GtkTextIter end = match_pos;
1454
1455
                gtk_text_iter_forward_chars(&end, len);
1456
                gtk_text_buffer_select_range(buffer, &match_pos, &end);
1457
                mark = gtk_text_buffer_get_insert(buffer);
1458
                gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1459
                return TRUE;
1460
        }
1461
1462
        return FALSE;
1463
}
1464
1465
void textview_scroll_one_line(TextView *textview, gboolean up)
1466
{
1467
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1468
        GtkAdjustment *vadj = text->vadjustment;
1469
        gfloat upper;
1470
1471
        if (prefs_common.enable_smooth_scroll) {
1472
                textview_smooth_scroll_one_line(textview, up);
1473
                return;
1474
        }
1475
1476
        if (!up) {
1477
                upper = vadj->upper - vadj->page_size;
1478
                if (vadj->value < upper) {
1479
                        vadj->value += vadj->step_increment;
1480
                        vadj->value = MIN(vadj->value, upper);
1481
                        g_signal_emit_by_name(G_OBJECT(vadj),
1482
                                              "value_changed", 0);
1483
                }
1484
        } else {
1485
                if (vadj->value > 0.0) {
1486
                        vadj->value -= vadj->step_increment;
1487
                        vadj->value = MAX(vadj->value, 0.0);
1488
                        g_signal_emit_by_name(G_OBJECT(vadj),
1489
                                              "value_changed", 0);
1490
                }
1491
        }
1492
}
1493
1494
gboolean textview_scroll_page(TextView *textview, gboolean up)
1495
{
1496
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1497
        GtkAdjustment *vadj = text->vadjustment;
1498
        gfloat upper;
1499
        gfloat page_incr;
1500
1501
        if (prefs_common.enable_smooth_scroll)
1502
                return textview_smooth_scroll_page(textview, up);
1503
1504
        if (prefs_common.scroll_halfpage)
1505
                page_incr = vadj->page_increment / 2;
1506
        else
1507
                page_incr = vadj->page_increment;
1508
1509
        if (!up) {
1510
                upper = vadj->upper - vadj->page_size;
1511
                if (vadj->value < upper) {
1512
                        vadj->value += page_incr;
1513
                        vadj->value = MIN(vadj->value, upper);
1514
                        g_signal_emit_by_name(G_OBJECT(vadj),
1515
                                              "value_changed", 0);
1516
                } else
1517
                        return FALSE;
1518
        } else {
1519
                if (vadj->value > 0.0) {
1520
                        vadj->value -= page_incr;
1521
                        vadj->value = MAX(vadj->value, 0.0);
1522
                        g_signal_emit_by_name(G_OBJECT(vadj),
1523
                                              "value_changed", 0);
1524
                } else
1525
                        return FALSE;
1526
        }
1527
1528
        return TRUE;
1529
}
1530
1531
static void textview_smooth_scroll_do(TextView *textview,
1532
                                      gfloat old_value, gfloat last_value,
1533
                                      gint step)
1534
{
1535
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1536
        GtkAdjustment *vadj = text->vadjustment;
1537
        gint change_value;
1538
        gboolean up;
1539
        gint i;
1540
1541
        if (old_value < last_value) {
1542
                change_value = last_value - old_value;
1543
                up = FALSE;
1544
        } else {
1545
                change_value = old_value - last_value;
1546
                up = TRUE;
1547
        }
1548
1549
        /* gdk_key_repeat_disable(); */
1550
1551
        g_signal_handlers_block_by_func(vadj, textview_adj_value_changed,
1552
                                        textview);
1553
1554
        for (i = step; i <= change_value; i += step) {
1555
                vadj->value = old_value + (up ? -i : i);
1556
                g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1557
        }
1558
1559
        g_signal_handlers_unblock_by_func(vadj, textview_adj_value_changed,
1560
                                          textview);
1561
1562
        vadj->value = last_value;
1563
        g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1564
1565
        /* gdk_key_repeat_restore(); */
1566
1567
        gtk_widget_queue_draw(GTK_WIDGET(text));
1568
}
1569
1570
static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1571
{
1572
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1573
        GtkAdjustment *vadj = text->vadjustment;
1574
        gfloat upper;
1575
        gfloat old_value;
1576
        gfloat last_value;
1577
1578
        if (!up) {
1579
                upper = vadj->upper - vadj->page_size;
1580
                if (vadj->value < upper) {
1581
                        old_value = vadj->value;
1582
                        last_value = vadj->value + vadj->step_increment;
1583
                        last_value = MIN(last_value, upper);
1584
1585
                        textview_smooth_scroll_do(textview, old_value,
1586
                                                  last_value,
1587
                                                  prefs_common.scroll_step);
1588
                }
1589
        } else {
1590
                if (vadj->value > 0.0) {
1591
                        old_value = vadj->value;
1592
                        last_value = vadj->value - vadj->step_increment;
1593
                        last_value = MAX(last_value, 0.0);
1594
1595
                        textview_smooth_scroll_do(textview, old_value,
1596
                                                  last_value,
1597
                                                  prefs_common.scroll_step);
1598
                }
1599
        }
1600
}
1601
1602
static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1603
{
1604
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1605
        GtkAdjustment *vadj = text->vadjustment;
1606
        gfloat upper;
1607
        gfloat page_incr;
1608
        gfloat old_value;
1609
        gfloat last_value;
1610
1611
        if (prefs_common.scroll_halfpage)
1612
                page_incr = vadj->page_increment / 2;
1613
        else
1614
                page_incr = vadj->page_increment;
1615
1616
        if (!up) {
1617
                upper = vadj->upper - vadj->page_size;
1618
                if (vadj->value < upper) {
1619
                        old_value = vadj->value;
1620
                        last_value = vadj->value + page_incr;
1621
                        last_value = MIN(last_value, upper);
1622
1623
                        textview_smooth_scroll_do(textview, old_value,
1624
                                                  last_value,
1625
                                                  prefs_common.scroll_step);
1626
                } else
1627
                        return FALSE;
1628
        } else {
1629
                if (vadj->value > 0.0) {
1630
                        old_value = vadj->value;
1631
                        last_value = vadj->value - page_incr;
1632
                        last_value = MAX(last_value, 0.0);
1633
1634
                        textview_smooth_scroll_do(textview, old_value,
1635
                                                  last_value,
1636
                                                  prefs_common.scroll_step);
1637
                } else
1638
                        return FALSE;
1639
        }
1640
1641
        return TRUE;
1642
}
1643
1644
#define KEY_PRESS_EVENT_STOP() \
1645
        g_signal_stop_emission_by_name(G_OBJECT(widget), "key-press-event");
1646
1647
static gboolean textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1648
                                     TextView *textview)
1649
{
1650
        SummaryView *summaryview = NULL;
1651
        MessageView *messageview = textview->messageview;
1652
1653
        if (!event) return FALSE;
1654
        if (messageview->mainwin)
1655
                summaryview = messageview->mainwin->summaryview;
1656
1657
        switch (event->keyval) {
1658
        case GDK_Tab:
1659
        case GDK_Home:
1660
        case GDK_Left:
1661
        case GDK_Up:
1662
        case GDK_Right:
1663
        case GDK_Down:
1664
        case GDK_Page_Up:
1665
        case GDK_Page_Down:
1666
        case GDK_End:
1667
        case GDK_Control_L:
1668
        case GDK_Control_R:
1669
        case GDK_KP_Tab:
1670
        case GDK_KP_Home:
1671
        case GDK_KP_Left:
1672
        case GDK_KP_Up:
1673
        case GDK_KP_Right:
1674
        case GDK_KP_Down:
1675
        case GDK_KP_Page_Up:
1676
        case GDK_KP_Page_Down:
1677
        case GDK_KP_End:
1678
                break;
1679
        case GDK_space:
1680
        case GDK_KP_Space:
1681
                if (summaryview)
1682
                        summary_pass_key_press_event(summaryview, event);
1683
                else
1684
                        textview_scroll_page
1685
                                (textview,
1686
                                 (event->state &
1687
                                  (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1688
                break;
1689
        case GDK_BackSpace:
1690
                textview_scroll_page(textview, TRUE);
1691
                break;
1692
        case GDK_Return:
1693
        case GDK_KP_Enter:
1694
                textview_scroll_one_line
1695
                        (textview, (event->state &
1696
                                    (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1697
                break;
1698
        case GDK_Delete:
1699
        case GDK_KP_Delete:
1700
                if (summaryview)
1701
                        summary_pass_key_press_event(summaryview, event);
1702
                break;
1703
        case GDK_Escape:
1704
                if (summaryview && textview == messageview->textview)
1705
                        gtk_widget_grab_focus(summaryview->treeview);
1706
                else if (messageview->type == MVIEW_MIME &&
1707
                         textview == messageview->mimeview->textview)
1708
                        gtk_widget_grab_focus(messageview->mimeview->treeview);
1709
                break;
1710
        case GDK_n:
1711
        case GDK_N:
1712
        case GDK_p:
1713
        case GDK_P:
1714
        case GDK_y:
1715
        case GDK_t:
1716
        case GDK_l:
1717
                if (messageview->type == MVIEW_MIME &&
1718
                    textview == messageview->mimeview->textview) {
1719
                        KEY_PRESS_EVENT_STOP();
1720
                        mimeview_pass_key_press_event(messageview->mimeview,
1721
                                                      event);
1722
                        break;
1723
                }
1724
                /* fall through */
1725
        default:
1726
                if (summaryview &&
1727
                    event->window != messageview->mainwin->window->window) {
1728
                        GdkEventKey tmpev = *event;
1729
1730
                        tmpev.window = messageview->mainwin->window->window;
1731
                        KEY_PRESS_EVENT_STOP();
1732
                        gtk_widget_event(messageview->mainwin->window,
1733
                                         (GdkEvent *)&tmpev);
1734
                }
1735
                break;
1736
        }
1737
1738
        return FALSE;
1739
}
1740
1741
static gboolean textview_get_link_tag_bounds(TextView *textview,
1742
                                             GtkTextIter *iter,
1743
                                             GtkTextIter *start,
1744
                                             GtkTextIter *end)
1745
{
1746
        GSList *tags, *cur;
1747
        gboolean on_link = FALSE;
1748
1749
        tags = gtk_text_iter_get_tags(iter);
1750
        *start = *end = *iter;
1751
1752
        for (cur = tags; cur != NULL; cur = cur->next) {
1753
                GtkTextTag *tag = cur->data;
1754
                gchar *tag_name;
1755
1756
                g_object_get(G_OBJECT(tag), "name", &tag_name, NULL);
1757
                if (tag_name && !strcmp(tag_name, "link")) {
1758
                        if (!gtk_text_iter_begins_tag(start, tag))
1759
                                gtk_text_iter_backward_to_tag_toggle
1760
                                        (start, tag);
1761
                        if (!gtk_text_iter_ends_tag(end, tag))
1762
                                gtk_text_iter_forward_to_tag_toggle(end, tag);
1763
                        on_link = TRUE;
1764
                        g_free(tag_name);
1765
                        break;
1766
                }
1767
                if (tag_name)
1768
                        g_free(tag_name);
1769
        }
1770
1771
        if (tags)
1772
                g_slist_free(tags);
1773
1774
        return on_link;
1775
}
1776
1777
static RemoteURI *textview_get_uri(TextView *textview, GtkTextIter *start,
1778
                                   GtkTextIter *end)
1779
{
1780
        gint start_pos, end_pos;
1781
        GSList *cur;
1782
        RemoteURI *uri = NULL;
1783
1784
        start_pos = gtk_text_iter_get_offset(start);
1785
        end_pos = gtk_text_iter_get_offset(end);
1786
1787
        for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1788
                RemoteURI *uri_ = (RemoteURI *)cur->data;
1789
1790
                if (start_pos == uri_->start && end_pos == uri_->end) {
1791
                        uri = uri_;
1792
                        break;
1793
                }
1794
        }
1795
1796
        return uri;
1797
}
1798
1799
static gboolean textview_event_after(GtkWidget *widget, GdkEvent *event,
1800
                                     TextView *textview)
1801
{
1802
        GdkEventButton *bevent = (GdkEventButton *)event;
1803
        GtkTextBuffer *buffer;
1804
        GtkTextIter iter, start, end;
1805
        gint x, y;
1806
        gboolean on_link;
1807
        RemoteURI *uri;
1808
1809
        if (event->type != GDK_BUTTON_RELEASE)
1810
                return FALSE;
1811
        if (bevent->button != 1 && bevent->button != 2)
1812
                return FALSE;
1813
1814
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
1815
1816
        /* don't follow a link if the user has selected something */
1817
        gtk_text_buffer_get_selection_bounds(buffer, &start, &end);
1818
        if (!gtk_text_iter_equal(&start, &end))
1819
                return FALSE;
1820
1821
        gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget),
1822
                                              GTK_TEXT_WINDOW_WIDGET,
1823
                                              bevent->x, bevent->y, &x, &y);
1824
        gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &iter, x, y);
1825
        on_link = textview_get_link_tag_bounds(textview, &iter, &start, &end);
1826
        if (!on_link)
1827
                return FALSE;
1828
1829
        uri = textview_get_uri(textview, &start, &end);
1830
        if (!uri)
1831
                return FALSE;
1832
1833
        if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
1834
                PrefsAccount *ac = NULL;
1835
                MsgInfo *msginfo = textview->messageview->msginfo;
1836
1837
                if (msginfo && msginfo->folder)
1838
                        ac = account_find_from_item(msginfo->folder);
1839
                if (ac && ac->protocol == A_NNTP)
1840
                        ac = NULL;
1841
                compose_new(ac, msginfo->folder, uri->uri + 7, NULL);
1842
        } else if (textview_uri_security_check(textview, uri) == TRUE)
1843
                open_uri(uri->uri, prefs_common.uri_cmd);
1844
1845
        return FALSE;
1846
}
1847
1848
static void textview_show_uri(TextView *textview, GtkTextIter *start,
1849
                              GtkTextIter *end)
1850
{
1851
        RemoteURI *uri;
1852
1853
        STATUSBAR_POP(textview);
1854
        uri = textview_get_uri(textview, start, end);
1855
        if (uri) {
1856
                STATUSBAR_PUSH(textview, uri->uri);
1857
        }
1858
}
1859
1860
static void textview_set_cursor(TextView *textview, GtkTextView *text,
1861
                                gint x, gint y)
1862
{
1863
        GtkTextBuffer *buffer;
1864
        GtkTextIter iter;
1865
        GtkTextIter start, end;
1866
        GtkTextMark *start_mark, *end_mark;
1867
        gboolean on_link = FALSE;
1868
1869
        buffer = gtk_text_view_get_buffer(text);
1870
        gtk_text_view_get_iter_at_location(text, &iter, x, y);
1871
        on_link = textview_get_link_tag_bounds(textview, &iter, &start, &end);
1872
1873
        start_mark = gtk_text_buffer_get_mark(buffer, "hover-link-start");
1874
        end_mark = gtk_text_buffer_get_mark(buffer, "hover-link-end");
1875
        if (start_mark) {
1876
                GtkTextIter prev_start, prev_end;
1877
1878
                gtk_text_buffer_get_iter_at_mark(buffer, &prev_start,
1879
                                                 start_mark);
1880
                gtk_text_buffer_get_iter_at_mark(buffer, &prev_end, end_mark);
1881
                if (on_link) {
1882
                        if (gtk_text_iter_equal(&prev_start, &start))
1883
                                return;
1884
                }
1885
1886
                gtk_text_buffer_get_iter_at_mark(buffer, &prev_start,
1887
                                                 start_mark);
1888
                gtk_text_buffer_get_iter_at_mark(buffer, &prev_end, end_mark);
1889
                gtk_text_buffer_remove_tag_by_name(buffer, "hover-link",
1890
                                                   &prev_start, &prev_end);
1891
                gtk_text_buffer_delete_mark(buffer, start_mark);
1892
                gtk_text_buffer_delete_mark(buffer, end_mark);
1893
        } else {
1894
                if (!on_link)
1895
                        return;
1896
        }
1897
1898
        if (on_link) {
1899
                gtk_text_buffer_create_mark
1900
                        (buffer, "hover-link-start", &start, FALSE);
1901
                gtk_text_buffer_create_mark
1902
                        (buffer, "hover-link-end", &end, FALSE);
1903
                gtk_text_buffer_apply_tag_by_name
1904
                        (buffer, "hover-link", &start, &end);
1905
                gdk_window_set_cursor
1906
                        (gtk_text_view_get_window(text, GTK_TEXT_WINDOW_TEXT),
1907
                         hand_cursor);
1908
                textview_show_uri(textview, &start, &end);
1909
        } else {
1910
                gdk_window_set_cursor
1911
                        (gtk_text_view_get_window(text, GTK_TEXT_WINDOW_TEXT),
1912
                         regular_cursor);
1913
                STATUSBAR_POP(textview);
1914
        }
1915
}
1916
1917
static gboolean textview_motion_notify(GtkWidget *widget,
1918
                                       GdkEventMotion *event,
1919
                                       TextView *textview)
1920
{
1921
        gint x, y;
1922
1923
        gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget),
1924
                                              GTK_TEXT_WINDOW_WIDGET,
1925
                                              event->x, event->y, &x, &y);
1926
        textview_set_cursor(textview, GTK_TEXT_VIEW(widget), x, y);
1927
        gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1928
1929
        return FALSE;
1930
}
1931
1932
static gboolean textview_leave_notify(GtkWidget *widget,
1933
                                      GdkEventCrossing *event,
1934
                                      TextView *textview)
1935
{
1936
        textview_set_cursor(textview, GTK_TEXT_VIEW(widget), 0, 0);
1937
1938
        return FALSE;
1939
}
1940
1941
static gboolean textview_visibility_notify(GtkWidget *widget,
1942
                                           GdkEventVisibility *event,
1943
                                           TextView *textview)
1944
{
1945
        gint wx, wy, bx, by;
1946
        GdkWindow *window;
1947
1948
        window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1949
                                          GTK_TEXT_WINDOW_TEXT);
1950
1951
        /* check if the event occurred for the text window part */
1952
        if (window != event->window)
1953
                return FALSE;
1954
1955
        gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1956
        gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget),
1957
                                              GTK_TEXT_WINDOW_WIDGET,
1958
                                              wx, wy, &bx, &by);
1959
        textview_set_cursor(textview, GTK_TEXT_VIEW(widget), bx, by);
1960
1961
        return FALSE;
1962
}
1963
1964
#define ADD_MENU(label, func)                                                \
1965
{                                                                        \
1966
        menuitem = gtk_menu_item_new_with_mnemonic(label);                \
1967
        g_signal_connect(menuitem, "activate", G_CALLBACK(func), uri);        \
1968
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);                \
1969
        g_object_set_data(G_OBJECT(menuitem), "textview", textview);        \
1970
        gtk_widget_show(menuitem);                                        \
1971
}
1972
1973
static void textview_populate_popup(GtkWidget *widget, GtkMenu *menu,
1974
                                    TextView *textview)
1975
{
1976
        gint px, py, x, y;
1977
        GtkWidget *separator, *menuitem;
1978
        GtkTextBuffer *buffer;
1979
        GtkTextIter iter, start, end;
1980
        gboolean on_link;
1981
        RemoteURI *uri;
1982
        GdkPixbuf *pixbuf;
1983
1984
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
1985
1986
        gdk_window_get_pointer(widget->window, &px, &py, NULL);
1987
        gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget),
1988
                                              GTK_TEXT_WINDOW_WIDGET,
1989
                                              px, py, &x, &y);
1990
        gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &iter, x, y);
1991
        if ((pixbuf = gtk_text_iter_get_pixbuf(&iter)) != NULL) {
1992
                start = end = iter;
1993
                gtk_text_iter_forward_char(&end);
1994
                uri = textview_get_uri(textview, &start, &end);
1995
1996
                separator = gtk_separator_menu_item_new();
1997
                gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator);
1998
                gtk_widget_show(separator);
1999
2000
                ADD_MENU(_("Sa_ve this image as..."),
2001
                         textview_popup_menu_activate_image_cb);
2002
        }
2003
        on_link = textview_get_link_tag_bounds(textview, &iter, &start, &end);
2004
        if (!on_link)
2005
                return;
2006
2007
        uri = textview_get_uri(textview, &start, &end);
2008
        if (!uri)
2009
                return;
2010
2011
        separator = gtk_separator_menu_item_new();
2012
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator);
2013
        gtk_widget_show(separator);
2014
2015
        if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2016
                ADD_MENU(_("Compose _new message"),
2017
                         textview_popup_menu_activate_open_uri_cb);
2018
                ADD_MENU(_("Add to address _book..."),
2019
                         textview_popup_menu_activate_add_address_cb);
2020
                ADD_MENU(_("Copy this add_ress"),
2021
                         textview_popup_menu_activate_copy_cb);
2022
        } else {
2023
                ADD_MENU(_("_Open with Web browser"),
2024
                         textview_popup_menu_activate_open_uri_cb);
2025
                ADD_MENU(_("Copy this _link"),
2026
                         textview_popup_menu_activate_copy_cb);
2027
        }
2028
}
2029
2030
static void textview_popup_menu_activate_open_uri_cb(GtkMenuItem *menuitem,
2031
                                                     gpointer data)
2032
{
2033
        RemoteURI *uri = (RemoteURI *)data;
2034
        TextView *textview;
2035
2036
        g_return_if_fail(uri != NULL);
2037
2038
        if (!uri->uri)
2039
                return;
2040
2041
        textview = g_object_get_data(G_OBJECT(menuitem), "textview");
2042
        g_return_if_fail(textview != NULL);
2043
2044
        if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2045
                PrefsAccount *ac = NULL;
2046
                MsgInfo *msginfo = textview->messageview->msginfo;
2047
2048
                if (msginfo && msginfo->folder)
2049
                        ac = account_find_from_item(msginfo->folder);
2050
                if (ac && ac->protocol == A_NNTP)
2051
                        ac = NULL;
2052
                compose_new(ac, msginfo->folder, uri->uri + 7, NULL);
2053
        } else if (textview_uri_security_check(textview, uri) == TRUE)
2054
                open_uri(uri->uri, prefs_common.uri_cmd);
2055
}
2056
2057
static void textview_popup_menu_activate_add_address_cb(GtkMenuItem *menuitem,
2058
                                                        gpointer data)
2059
{
2060
        RemoteURI *uri = (RemoteURI *)data;
2061
        gchar *addr;
2062
2063
        g_return_if_fail(uri != NULL);
2064
2065
        if (!uri->uri)
2066
                return;
2067
2068
        if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2069
                addr = g_malloc(strlen(uri->uri + 7) + 1);
2070
                decode_uri(addr, uri->uri + 7);
2071
        } else
2072
                addr = g_strdup(uri->uri);
2073
2074
        addressbook_add_contact(addr, addr, NULL);
2075
2076
        g_free(addr);
2077
}
2078
2079
static void textview_popup_menu_activate_copy_cb(GtkMenuItem *menuitem,
2080
                                                 gpointer data)
2081
{
2082
        RemoteURI *uri = (RemoteURI *)data;
2083
        gchar *uri_string;
2084
        GtkClipboard *clipboard;
2085
2086
        g_return_if_fail(uri != NULL);
2087
2088
        if (!uri->uri)
2089
                return;
2090
2091
        if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2092
                uri_string = g_malloc(strlen(uri->uri + 7) + 1);
2093
                decode_uri(uri_string, uri->uri + 7);
2094
        } else
2095
                uri_string = g_strdup(uri->uri);
2096
2097
        clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2098
        gtk_clipboard_set_text(clipboard, uri_string, -1);
2099
        clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
2100
        gtk_clipboard_set_text(clipboard, uri_string, -1);
2101
2102
        g_free(uri_string);
2103
}
2104
2105
static void textview_popup_menu_activate_image_cb(GtkMenuItem *menuitem,
2106
                                                  gpointer data)
2107
{
2108
        RemoteURI *uri = (RemoteURI *)data;
2109
        gchar *src;
2110
        gchar *dest;
2111
        gchar *filename;
2112
2113
        g_return_if_fail(uri != NULL);
2114
2115
        if (!uri->uri)
2116
                return;
2117
2118
        src = g_filename_from_uri(uri->uri, NULL, NULL);
2119
        g_return_if_fail(src != NULL);
2120
2121
        filename = conv_filename_to_utf8(uri->filename);
2122
        dest = filesel_save_as(filename);
2123
        if (dest) {
2124
                copy_file(src, dest, FALSE);
2125
                g_free(dest);
2126
        }
2127
        g_free(filename);
2128
        g_free(src);
2129
}
2130
2131
static void textview_adj_value_changed(GtkAdjustment *adj, gpointer data)
2132
{
2133
        TextView *textview = (TextView *)data;
2134
        GtkTextBuffer *buffer;
2135
2136
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2137
        if (gtk_text_buffer_get_selection_bounds(buffer, NULL, NULL))
2138
                return;
2139
        gtk_text_view_place_cursor_onscreen(GTK_TEXT_VIEW(textview->text));
2140
}
2141
2142
static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2143
{
2144
        GtkTextBuffer *buffer;
2145
        GtkTextIter start_iter, end_iter;
2146
        gchar *visible_str;
2147
        gboolean retval = TRUE;
2148
2149
        if (is_uri_string(uri->uri) == FALSE)
2150
                return TRUE;
2151
2152
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2153
        gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, uri->start);
2154
        gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, uri->end);
2155
        visible_str = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter,
2156
                                               FALSE);
2157
        if (visible_str == NULL)
2158
                return TRUE;
2159
2160
        if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2161
                gchar *uri_path;
2162
                gchar *visible_uri_path;
2163
2164
                uri_path = get_uri_path(uri->uri);
2165
                visible_uri_path = get_uri_path(visible_str);
2166
                if (path_cmp(uri_path, visible_uri_path) != 0)
2167
                        retval = FALSE;
2168
        }
2169
2170
        if (retval == FALSE) {
2171
                gchar *msg;
2172
                AlertValue aval;
2173
2174
                msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2175
                                        "the apparent URL (%s).\n"
2176
                                        "\n"
2177
                                        "Open it anyway?"),
2178
                                      uri->uri, visible_str);
2179
                aval = alertpanel_full(_("Fake URL warning"), msg,
2180
                                       ALERT_WARNING, G_ALERTDEFAULT, FALSE,
2181
                                       GTK_STOCK_YES, GTK_STOCK_NO, NULL);
2182
                g_free(msg);
2183
                if (aval == G_ALERTDEFAULT)
2184
                        retval = TRUE;
2185
        }
2186
2187
        g_free(visible_str);
2188
2189
        return retval;
2190
}
2191
2192
static void textview_uri_list_remove_all(GSList *uri_list)
2193
{
2194
        GSList *cur;
2195
2196
        for (cur = uri_list; cur != NULL; cur = cur->next) {
2197
                RemoteURI *uri = (RemoteURI *)cur->data;
2198
2199
                if (uri) {
2200
                        g_free(uri->uri);
2201
                        g_free(uri->filename);
2202
                        g_free(uri);
2203
                }
2204
        }
2205
2206
        g_slist_free(uri_list);
2207
}