Statistics
| Revision:

root / src / textview.c @ 500

History | View | Annotate | Download (54.8 kB)

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