Statistics
| Revision:

root / src / textview.c @ 2821

History | View | Annotate | Download (59.3 KB)

1
/*
2
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3
 * Copyright (C) 1999-2011 Hiroyuki Yamamoto
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
 */
19

    
20
#ifdef HAVE_CONFIG_H
21
#  include "config.h"
22
#endif
23

    
24
#include "defs.h"
25

    
26
#include <glib.h>
27
#include <glib/gi18n.h>
28
#include <gdk/gdk.h>
29
#include <gdk/gdkkeysyms.h>
30
#include <gdk-pixbuf/gdk-pixbuf.h>
31
#include <gtk/gtkvbox.h>
32
#include <gtk/gtkscrolledwindow.h>
33
#include <gtk/gtksignal.h>
34
#include <gtk/gtkmenu.h>
35
#include <gtk/gtkmenuitem.h>
36
#include <gtk/gtkseparatormenuitem.h>
37
#include <gtk/gtkstock.h>
38
#include <stdio.h>
39
#include <ctype.h>
40
#include <string.h>
41
#include <stdlib.h>
42

    
43
#include "main.h"
44
#include "summaryview.h"
45
#include "imageview.h"
46
#include "procheader.h"
47
#include "prefs_common.h"
48
#include "codeconv.h"
49
#include "statusbar.h"
50
#include "utils.h"
51
#include "gtkutils.h"
52
#include "procmime.h"
53
#include "account.h"
54
#include "html.h"
55
#include "compose.h"
56
#include "displayheader.h"
57
#include "filesel.h"
58
#include "alertpanel.h"
59
#include "plugin.h"
60

    
61
typedef struct _RemoteURI        RemoteURI;
62

    
63
struct _RemoteURI
64
{
65
        gchar *uri;
66

    
67
        gchar *filename;
68

    
69
        guint start;
70
        guint end;
71
};
72

    
73
static GdkColor quote_colors[3] = {
74
        {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
75
        {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
76
        {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
77
};
78

    
79
static GdkColor uri_color = {
80
        (gulong)0,
81
        (gushort)0,
82
        (gushort)0,
83
        (gushort)0
84
};
85

    
86
static GdkColor emphasis_color = {
87
        (gulong)0,
88
        (gushort)0,
89
        (gushort)0,
90
        (gushort)0xcfff
91
};
92

    
93
static GdkColor error_color = {
94
        (gulong)0,
95
        (gushort)0xefff,
96
        (gushort)0,
97
        (gushort)0
98
};
99

    
100
#if USE_GPGME
101
static GdkColor good_sig_color = {
102
        (gulong)0,
103
        (gushort)0,
104
        (gushort)0xbfff,
105
        (gushort)0
106
};
107

    
108
static GdkColor untrusted_sig_color = {
109
        (gulong)0,
110
        (gushort)0xefff,
111
        (gushort)0,
112
        (gushort)0
113
};
114

    
115
static GdkColor nocheck_sig_color = {
116
        (gulong)0,
117
        (gushort)0,
118
        (gushort)0,
119
        (gushort)0xcfff
120
};
121

    
122
static GdkColor bad_sig_color = {
123
        (gulong)0,
124
        (gushort)0xefff,
125
        (gushort)0,
126
        (gushort)0
127
};
128
#endif
129

    
130
#define STATUSBAR_PUSH(textview, str)                                            \
131
{                                                                            \
132
        gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
133
                           textview->messageview->statusbar_cid, str);            \
134
}
135

    
136
#define STATUSBAR_POP(textview)                                                   \
137
{                                                                           \
138
        gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
139
                          textview->messageview->statusbar_cid);           \
140
}
141

    
142
static GdkCursor *hand_cursor = NULL;
143
static GdkCursor *regular_cursor = NULL;
144

    
145

    
146
static void textview_add_part                (TextView        *textview,
147
                                         MimeInfo        *mimeinfo,
148
                                         FILE                *fp);
149
#if USE_GPGME
150
static void textview_add_sig_part        (TextView        *textview,
151
                                         MimeInfo        *mimeinfo);
152
#endif
153
static void textview_add_parts                (TextView        *textview,
154
                                         MimeInfo        *mimeinfo,
155
                                         FILE                *fp);
156
static void textview_write_body                (TextView        *textview,
157
                                         MimeInfo        *mimeinfo,
158
                                         FILE                *fp,
159
                                         const gchar        *charset);
160
static void textview_show_html                (TextView        *textview,
161
                                         FILE                *fp,
162
                                         CodeConverter        *conv);
163

    
164
static void textview_write_line                (TextView        *textview,
165
                                         const gchar        *str,
166
                                         CodeConverter        *conv);
167
static void textview_write_link                (TextView        *textview,
168
                                         const gchar        *str,
169
                                         const gchar        *uri,
170
                                         CodeConverter        *conv);
171

    
172
static GPtrArray *textview_scan_header        (TextView        *textview,
173
                                         FILE                *fp,
174
                                         const gchar        *encoding);
175
static void textview_show_header        (TextView        *textview,
176
                                         GPtrArray        *headers);
177

    
178
static gboolean textview_key_pressed                (GtkWidget        *widget,
179
                                                 GdkEventKey        *event,
180
                                                 TextView        *textview);
181
static gboolean textview_event_after                (GtkWidget        *widget,
182
                                                 GdkEvent        *event,
183
                                                 TextView        *textview);
184
static gboolean textview_motion_notify                (GtkWidget        *widget,
185
                                                 GdkEventMotion        *event,
186
                                                 TextView        *textview);
187
static gboolean textview_leave_notify                (GtkWidget          *widget,
188
                                                 GdkEventCrossing *event,
189
                                                 TextView          *textview);
190
static gboolean textview_visibility_notify        (GtkWidget        *widget,
191
                                                 GdkEventVisibility *event,
192
                                                 TextView        *textview);
193

    
194
static void textview_populate_popup                (GtkWidget        *widget,
195
                                                 GtkMenu        *menu,
196
                                                 TextView        *textview);
197
static void textview_popup_menu_activate_open_uri_cb
198
                                                (GtkMenuItem        *menuitem,
199
                                                 gpointer         data);
200
static void textview_popup_menu_activate_reply_cb
201
                                                (GtkMenuItem        *menuitem,
202
                                                 gpointer         data);
203
static void textview_popup_menu_activate_add_address_cb
204
                                                (GtkMenuItem        *menuitem,
205
                                                 gpointer         data);
206
static void textview_popup_menu_activate_copy_cb(GtkMenuItem        *menuitem,
207
                                                 gpointer         data);
208
static void textview_popup_menu_activate_image_cb
209
                                                (GtkMenuItem        *menuitem,
210
                                                 gpointer         data);
211

    
212
static void textview_adj_value_changed                (GtkAdjustment        *adj,
213
                                                 gpointer         data);
214

    
215
static void textview_smooth_scroll_do                (TextView        *textview,
216
                                                 gfloat                 old_value,
217
                                                 gfloat                 last_value,
218
                                                 gint                 step);
219
static void textview_smooth_scroll_one_line        (TextView        *textview,
220
                                                 gboolean         up);
221
static gboolean textview_smooth_scroll_page        (TextView        *textview,
222
                                                 gboolean         up);
223

    
224
static gboolean textview_get_link_tag_bounds        (TextView        *textview,
225
                                                 GtkTextIter        *iter,
226
                                                 GtkTextIter        *start,
227
                                                 GtkTextIter        *end);
228
static RemoteURI *textview_get_uri                (TextView        *textview,
229
                                                 GtkTextIter        *start,
230
                                                 GtkTextIter        *end);
231
static void textview_show_uri                        (TextView        *textview,
232
                                                 GtkTextIter        *start,
233
                                                 GtkTextIter        *end);
234
static void textview_set_cursor                        (TextView        *textview,
235
                                                 GtkTextView        *text,
236
                                                 gint                 x,
237
                                                 gint                 y);
238

    
239
static gboolean textview_uri_security_check        (TextView        *textview,
240
                                                 RemoteURI        *uri);
241
static void textview_uri_list_remove_all        (GSList                *uri_list);
242

    
243

    
244
TextView *textview_create(void)
245
{
246
        TextView *textview;
247
        GtkWidget *vbox;
248
        GtkWidget *scrolledwin;
249
        GtkWidget *text;
250
        GtkTextBuffer *buffer;
251
        GtkClipboard *clipboard;
252

    
253
        debug_print(_("Creating text view...\n"));
254
        textview = g_new0(TextView, 1);
255

    
256
        scrolledwin = gtk_scrolled_window_new(NULL, NULL);
257
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
258
                                       GTK_POLICY_AUTOMATIC,
259
                                       GTK_POLICY_AUTOMATIC);
260
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
261
                                            GTK_SHADOW_ETCHED_IN);
262
        gtk_widget_set_size_request
263
                (scrolledwin, prefs_common.mainview_width, -1);
264

    
265
        text = gtk_text_view_new();
266
        gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
267
        gtk_widget_show(text);
268
        gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
269
        gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
270
        gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
271
        gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
272

    
273
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
274
        clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
275
        gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
276

    
277
        gtk_widget_ref(scrolledwin);
278

    
279
        gtk_container_add(GTK_CONTAINER(scrolledwin), text);
280

    
281
        g_signal_connect(G_OBJECT(text), "key-press-event",
282
                         G_CALLBACK(textview_key_pressed), textview);
283
        g_signal_connect(G_OBJECT(text), "event-after",
284
                         G_CALLBACK(textview_event_after), textview);
285
        g_signal_connect(G_OBJECT(text), "motion-notify-event",
286
                         G_CALLBACK(textview_motion_notify), textview);
287
        g_signal_connect(G_OBJECT(text), "leave-notify-event",
288
                         G_CALLBACK(textview_leave_notify), textview);
289
        g_signal_connect(G_OBJECT(text), "visibility-notify-event",
290
                         G_CALLBACK(textview_visibility_notify), textview);
291
        g_signal_connect(G_OBJECT(text), "populate-popup",
292
                         G_CALLBACK(textview_populate_popup), textview);
293

    
294
        g_signal_connect(G_OBJECT(GTK_TEXT_VIEW(text)->vadjustment),
295
                         "value-changed",
296
                         G_CALLBACK(textview_adj_value_changed), textview);
297

    
298
        gtk_widget_show(scrolledwin);
299

    
300
        vbox = gtk_vbox_new(FALSE, 0);
301
        gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
302

    
303
        gtk_widget_show(vbox);
304

    
305
        textview->vbox             = vbox;
306
        textview->scrolledwin      = scrolledwin;
307
        textview->text             = text;
308
        textview->uri_list         = NULL;
309
        textview->body_pos         = 0;
310
        textview->show_all_headers = FALSE;
311

    
312
        return textview;
313
}
314

    
315
static void textview_create_tags(TextView *textview)
316
{
317
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
318
        GtkTextBuffer *buffer;
319
        static PangoFontDescription *font_desc;
320

    
321
        if (!font_desc)
322
                font_desc = gtkut_get_default_font_desc();
323

    
324
        buffer = gtk_text_view_get_buffer(text);
325

    
326
        gtk_text_buffer_create_tag(buffer, "header",
327
                                   "pixels-above-lines", 1,
328
                                   "pixels-above-lines-set", TRUE,
329
                                   "pixels-below-lines", 0,
330
                                   "pixels-below-lines-set", TRUE,
331
                                   "font-desc", font_desc,
332
                                   NULL);
333
        gtk_text_buffer_create_tag(buffer, "header_title",
334
                                   "weight", PANGO_WEIGHT_BOLD,
335
                                   NULL);
336

    
337
        gtk_text_buffer_create_tag(buffer, "mimepart",
338
                                   "pixels-above-lines", 1,
339
                                   "pixels-above-lines-set", TRUE,
340
                                   "pixels-below-lines", 1,
341
                                   "pixels-below-lines-set", TRUE,
342
                                   "font-desc", font_desc,
343
                                   NULL);
344

    
345
        textview->quote0_tag =
346
                gtk_text_buffer_create_tag(buffer, "quote0",
347
                                           "foreground-gdk", &quote_colors[0],
348
                                           NULL);
349
        textview->quote1_tag =
350
                gtk_text_buffer_create_tag(buffer, "quote1",
351
                                           "foreground-gdk", &quote_colors[1],
352
                                           NULL);
353
        textview->quote2_tag =
354
                gtk_text_buffer_create_tag(buffer, "quote2",
355
                                           "foreground-gdk", &quote_colors[2],
356
                                           NULL);
357
        textview->link_tag =
358
                gtk_text_buffer_create_tag(buffer, "link",
359
                                           "foreground-gdk", &uri_color,
360
                                           NULL);
361
        textview->hover_link_tag =
362
                gtk_text_buffer_create_tag(buffer, "hover-link",
363
                                           "foreground-gdk", &uri_color,
364
                                           "underline", PANGO_UNDERLINE_SINGLE,
365
                                           NULL);
366

    
367
        gtk_text_buffer_create_tag(buffer, "emphasis",
368
                                   "foreground-gdk", &emphasis_color,
369
                                   NULL);
370
        gtk_text_buffer_create_tag(buffer, "error",
371
                                   "foreground-gdk", &error_color,
372
                                   NULL);
373
#if USE_GPGME
374
        gtk_text_buffer_create_tag(buffer, "good-signature",
375
                                   "foreground-gdk", &good_sig_color,
376
                                   NULL);
377
        gtk_text_buffer_create_tag(buffer, "untrusted-signature",
378
                                   "foreground-gdk", &untrusted_sig_color,
379
                                   NULL);
380
        gtk_text_buffer_create_tag(buffer, "bad-signature",
381
                                   "foreground-gdk", &bad_sig_color,
382
                                   NULL);
383
        gtk_text_buffer_create_tag(buffer, "nocheck-signature",
384
                                   "foreground-gdk", &nocheck_sig_color,
385
                                   NULL);
386
#endif /* USE_GPGME */
387
}
388

    
389
void textview_init(TextView *textview)
390
{
391
        if (!hand_cursor)
392
                hand_cursor = gdk_cursor_new(GDK_HAND2);
393
        if (!regular_cursor)
394
                regular_cursor = gdk_cursor_new(GDK_XTERM);
395

    
396
        textview_create_tags(textview);
397
        textview_reflect_prefs(textview);
398
        textview_set_all_headers(textview, FALSE);
399
        textview_set_font(textview, NULL);
400
}
401

    
402
static void textview_update_message_colors(void)
403
{
404
        GdkColor black = {0, 0, 0, 0};
405

    
406
        if (prefs_common.enable_color) {
407
                /* grab the quote colors, converting from an int to a
408
                   GdkColor */
409
                gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
410
                                               &quote_colors[0]);
411
                gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
412
                                               &quote_colors[1]);
413
                gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
414
                                               &quote_colors[2]);
415
                gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
416
                                               &uri_color);
417
        } else {
418
                quote_colors[0] = quote_colors[1] = quote_colors[2] = 
419
                        uri_color = emphasis_color = black;
420
        }
421
}
422

    
423
static void textview_update_tags(TextView *textview)
424
{
425
        g_object_set(textview->quote0_tag, "foreground-gdk", &quote_colors[0],
426
                     NULL);
427
        g_object_set(textview->quote1_tag, "foreground-gdk", &quote_colors[1],
428
                     NULL);
429
        g_object_set(textview->quote2_tag, "foreground-gdk", &quote_colors[2],
430
                     NULL);
431
        g_object_set(textview->link_tag, "foreground-gdk", &uri_color, NULL);
432
        g_object_set(textview->hover_link_tag, "foreground-gdk", &uri_color,
433
                     NULL);
434
}
435

    
436
void textview_reflect_prefs(TextView *textview)
437
{
438
        textview_update_message_colors();
439
        textview_update_tags(textview);
440
        gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview->text),
441
                                         prefs_common.textview_cursor_visible);
442
}
443

    
444
static const gchar *textview_get_src_encoding(TextView *textview,
445
                                              MimeInfo *mimeinfo)
446
{
447
        const gchar *charset;
448

    
449
        if (textview->messageview->forced_charset)
450
                charset = textview->messageview->forced_charset;
451
        else if (!textview->messageview->new_window &&
452
                 prefs_common.force_charset)
453
                charset = prefs_common.force_charset;
454
        else if (mimeinfo->charset)
455
                charset = mimeinfo->charset;
456
        else if (prefs_common.default_encoding)
457
                charset = prefs_common.default_encoding;
458
        else
459
                charset = NULL;
460

    
461
        return charset;
462
}
463

    
464
void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
465
                           const gchar *file)
466
{
467
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
468
        GtkTextBuffer *buffer;
469
        GtkTextMark *mark;
470
        GtkTextIter iter;
471
        FILE *fp;
472
        const gchar *charset;
473
        GPtrArray *headers = NULL;
474

    
475
        buffer = gtk_text_view_get_buffer(text);
476

    
477
        if ((fp = g_fopen(file, "rb")) == NULL) {
478
                FILE_OP_ERROR(file, "fopen");
479
                return;
480
        }
481

    
482
        charset = textview_get_src_encoding(textview, mimeinfo);
483
        textview_set_font(textview, charset);
484
        textview_clear(textview);
485

    
486
        if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) perror("fseek");
487
        headers = textview_scan_header(textview, fp, charset);
488
        if (headers) {
489
                textview_show_header(textview, headers);
490
                procheader_header_array_destroy(headers);
491

    
492
                gtk_text_buffer_get_end_iter(buffer, &iter);
493
                textview->body_pos = gtk_text_iter_get_offset(&iter);
494
        }
495

    
496
#if USE_GPGME
497
        if (textview->messageview->msginfo->encinfo &&
498
            textview->messageview->msginfo->encinfo->decryption_failed) {
499
                gtk_text_buffer_get_end_iter(buffer, &iter);
500
                gtk_text_buffer_insert(buffer, &iter, "\n", 1);
501
                gtk_text_buffer_insert_with_tags_by_name
502
                        (buffer, &iter, _("This message is encrypted, but its decryption failed.\n"),
503
                         -1, "error", "mimepart", NULL);
504
        }
505
#endif
506

    
507
        textview_add_parts(textview, mimeinfo, fp);
508

    
509
#if USE_GPGME
510
        if (textview->messageview->msginfo->encinfo &&
511
            textview->messageview->msginfo->encinfo->sigstatus)
512
                textview_add_sig_part(textview, NULL);
513
#endif
514

    
515
        fclose(fp);
516

    
517
        textview_set_position(textview, 0);
518
        mark = gtk_text_buffer_get_insert(buffer);
519
        gtk_text_view_scroll_mark_onscreen(text, mark);
520
}
521

    
522
void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
523
{
524
        gchar buf[BUFFSIZE];
525
        const gchar *boundary = NULL;
526
        gint boundary_len = 0;
527
        const gchar *charset;
528
        GPtrArray *headers = NULL;
529
        gboolean is_rfc822_part = FALSE;
530
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
531
        GtkTextBuffer *buffer;
532
        GtkTextIter iter;
533
        GtkTextMark *mark;
534

    
535
        g_return_if_fail(mimeinfo != NULL);
536
        g_return_if_fail(fp != NULL);
537

    
538
        if (mimeinfo->mime_type == MIME_MULTIPART) {
539
                textview_clear(textview);
540
                textview_add_parts(textview, mimeinfo, fp);
541
                return;
542
        }
543

    
544
        if (mimeinfo->parent && mimeinfo->parent->boundary) {
545
                boundary = mimeinfo->parent->boundary;
546
                boundary_len = strlen(boundary);
547
        }
548

    
549
        charset = textview_get_src_encoding(textview, mimeinfo);
550

    
551
        if (!boundary && mimeinfo->mime_type == MIME_TEXT) {
552
                if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0)
553
                        perror("fseek");
554
                headers = textview_scan_header(textview, fp, charset);
555
        } else {
556
                if (mimeinfo->mime_type == MIME_TEXT && mimeinfo->parent) {
557
                        glong fpos;
558
                        MimeInfo *parent = mimeinfo->parent;
559

    
560
                        while (parent->parent) {
561
                                if (parent->main &&
562
                                    parent->main->mime_type ==
563
                                        MIME_MESSAGE_RFC822)
564
                                        break;
565
                                parent = parent->parent;
566
                        }
567

    
568
                        if ((fpos = ftell(fp)) < 0)
569
                                perror("ftell");
570
                        else if (fseek(fp, parent->fpos, SEEK_SET) < 0)
571
                                perror("fseek");
572
                        else {
573
                                headers = textview_scan_header
574
                                        (textview, fp, charset);
575
                                if (fseek(fp, fpos, SEEK_SET) < 0)
576
                                        perror("fseek");
577
                        }
578
                }
579
                /* skip MIME part headers */
580
                while (fgets(buf, sizeof(buf), fp) != NULL)
581
                        if (buf[0] == '\r' || buf[0] == '\n') break;
582
        }
583

    
584
        /* display attached RFC822 single text message */
585
        if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
586
                if (headers) procheader_header_array_destroy(headers);
587
                if (!mimeinfo->sub) {
588
                        textview_clear(textview);
589
                        return;
590
                }
591
                headers = textview_scan_header(textview, fp, charset);
592
                mimeinfo = mimeinfo->sub;
593
                is_rfc822_part = TRUE;
594
        }
595

    
596
        textview_set_font(textview, charset);
597
        textview_clear(textview);
598

    
599
        buffer = gtk_text_view_get_buffer(text);
600

    
601
        if (headers) {
602
                textview_show_header(textview, headers);
603
                procheader_header_array_destroy(headers);
604

    
605
                gtk_text_buffer_get_end_iter(buffer, &iter);
606
                textview->body_pos = gtk_text_iter_get_offset(&iter);
607
                if (!mimeinfo->main)
608
                        gtk_text_buffer_insert(buffer, &iter, "\n", 1);
609
        }
610

    
611
        if (mimeinfo->mime_type == MIME_MULTIPART || is_rfc822_part)
612
                textview_add_parts(textview, mimeinfo, fp);
613
        else
614
                textview_write_body(textview, mimeinfo, fp, charset);
615

    
616
        textview_set_position(textview, 0);
617
        mark = gtk_text_buffer_get_insert(buffer);
618
        gtk_text_view_scroll_mark_onscreen(text, mark);
619
}
620

    
621
static void textview_add_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
622
{
623
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
624
        GtkTextBuffer *buffer;
625
        GtkTextIter iter;
626
        gchar buf[BUFFSIZE];
627
        const gchar *boundary = NULL;
628
        gint boundary_len = 0;
629
        const gchar *charset;
630
        GPtrArray *headers = NULL;
631

    
632
        g_return_if_fail(mimeinfo != NULL);
633
        g_return_if_fail(fp != NULL);
634

    
635
        buffer = gtk_text_view_get_buffer(text);
636
        gtk_text_buffer_get_end_iter(buffer, &iter);
637

    
638
        if (mimeinfo->mime_type == MIME_MULTIPART) return;
639

    
640
        if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) {
641
                perror("fseek");
642
                return;
643
        }
644

    
645
        if (mimeinfo->parent && mimeinfo->parent->boundary) {
646
                boundary = mimeinfo->parent->boundary;
647
                boundary_len = strlen(boundary);
648
        }
649

    
650
        while (fgets(buf, sizeof(buf), fp) != NULL)
651
                if (buf[0] == '\r' || buf[0] == '\n') break;
652

    
653
        charset = textview_get_src_encoding(textview, mimeinfo);
654

    
655
        if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
656
                headers = textview_scan_header(textview, fp, charset);
657
                if (headers) {
658
                        gtk_text_buffer_insert(buffer, &iter, "\n", 1);
659
                        textview_show_header(textview, headers);
660
                        procheader_header_array_destroy(headers);
661
                }
662
                return;
663
        }
664

    
665
#if USE_GPGME
666
        if (mimeinfo->parent && mimeinfo->sigstatus) {
667
                textview_add_sig_part(textview, mimeinfo);
668
                return;
669
        }
670
#endif
671

    
672
        if (mimeinfo->filename || mimeinfo->name)
673
                g_snprintf(buf, sizeof(buf), "\n[%s  %s (%s)]\n",
674
                           mimeinfo->filename ? mimeinfo->filename :
675
                           mimeinfo->name,
676
                           mimeinfo->content_type,
677
                           to_human_readable(mimeinfo->content_size));
678
        else
679
                g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n",
680
                           mimeinfo->content_type,
681
                           to_human_readable(mimeinfo->content_size));
682

    
683
        if (mimeinfo->mime_type != MIME_TEXT &&
684
            mimeinfo->mime_type != MIME_TEXT_HTML) {
685
                gtk_text_buffer_insert_with_tags_by_name
686
                        (buffer, &iter, buf, -1, "mimepart", NULL);
687
                if (mimeinfo->mime_type == MIME_IMAGE &&
688
                    prefs_common.inline_image) {
689
                        GdkPixbuf *pixbuf;
690
                        GError *error = NULL;
691
                        gchar *filename;
692
                        RemoteURI *uri;
693
                        gchar *uri_str;
694

    
695
                        filename = procmime_get_tmp_file_name(mimeinfo);
696
                        if (procmime_get_part_fp(filename, fp, mimeinfo) < 0) {
697
                                g_warning("Can't get the image file.");
698
                                g_free(filename);
699
                                return;
700
                        }
701

    
702
                        pixbuf = gdk_pixbuf_new_from_file(filename, &error);
703
                        if (error != NULL) {
704
                                g_warning("%s\n", error->message);
705
                                g_error_free(error);
706
                        }
707
                        if (!pixbuf) {
708
                                g_warning("Can't load the image.");
709
                                g_free(filename);
710
                                return;
711
                        }
712

    
713
                        if (prefs_common.resize_image) {
714
                                GdkPixbuf *scaled;
715

    
716
                                scaled = imageview_get_resized_pixbuf
717
                                        (pixbuf, textview->text, 8);
718
                                g_object_unref(pixbuf);
719
                                pixbuf = scaled;
720
                        }
721

    
722
                        uri_str = g_filename_to_uri(filename, NULL, NULL);
723
                        if (uri_str) {
724
                                uri = g_new(RemoteURI, 1);
725
                                uri->uri = uri_str;
726
                                uri->filename =
727
                                        procmime_get_part_file_name(mimeinfo);
728
                                uri->start = gtk_text_iter_get_offset(&iter);
729
                                uri->end = uri->start + 1;
730
                                textview->uri_list =
731
                                        g_slist_append(textview->uri_list, uri);
732
                        }
733
                        gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf);
734
                        gtk_text_buffer_insert(buffer, &iter, "\n", 1);
735

    
736
                        g_object_unref(pixbuf);
737
                        g_free(filename);
738
                }
739
        } else {
740
                if (!mimeinfo->main &&
741
                    mimeinfo->parent &&
742
                    mimeinfo->parent->children != mimeinfo)
743
                        gtk_text_buffer_insert_with_tags_by_name
744
                                (buffer, &iter, buf, -1, "mimepart", NULL);
745
                else
746
                        gtk_text_buffer_insert(buffer, &iter, "\n", 1);
747
                textview_write_body(textview, mimeinfo, fp, charset);
748
        }
749
}
750

    
751
#if USE_GPGME
752
static void textview_add_sig_part(TextView *textview, MimeInfo *mimeinfo)
753
{
754
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
755
        GtkTextBuffer *buffer;
756
        GtkTextIter iter;
757
        gchar buf[BUFFSIZE];
758
        const gchar *color;
759
        const gchar *sigstatus;
760
        const gchar *sigstatus_full;
761
        const gchar *type;
762

    
763
        if (mimeinfo) {
764
                sigstatus = mimeinfo->sigstatus;
765
                sigstatus_full = mimeinfo->sigstatus_full;
766
                type = mimeinfo->content_type;
767
        } else if (textview->messageview->msginfo->encinfo) {
768
                sigstatus = textview->messageview->msginfo->encinfo->sigstatus;
769
                sigstatus_full =
770
                        textview->messageview->msginfo->encinfo->sigstatus_full;
771
                type = "signature";
772
        } else
773
                return;
774

    
775
        if (!sigstatus)
776
                return;
777

    
778
        g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n", type, sigstatus);
779

    
780
        if (!strcmp(sigstatus, _("Good signature")))
781
                color = "good-signature";
782
        else if (!strcmp(sigstatus, _("Valid signature (untrusted key)")))
783
                color = "untrusted-signature";
784
        else if (!strcmp(sigstatus, _("BAD signature")))
785
                color = "bad-signature";
786
        else
787
                color = "nocheck-signature";
788

    
789
        buffer = gtk_text_view_get_buffer(text);
790
        gtk_text_buffer_get_end_iter(buffer, &iter);
791
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, buf, -1,
792
                                                 color, "mimepart", NULL);
793
        if (sigstatus_full)
794
                gtk_text_buffer_insert_with_tags_by_name
795
                        (buffer, &iter, sigstatus_full, -1, "mimepart", NULL);
796
}
797
#endif
798

    
799
static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
800
{
801
        gint level;
802

    
803
        g_return_if_fail(mimeinfo != NULL);
804
        g_return_if_fail(fp != NULL);
805

    
806
        level = mimeinfo->level;
807

    
808
        for (;;) {
809
                textview_add_part(textview, mimeinfo, fp);
810
                if (mimeinfo->parent && mimeinfo->parent->content_type &&
811
                    !g_ascii_strcasecmp(mimeinfo->parent->content_type,
812
                                        "multipart/alternative"))
813
                        mimeinfo = mimeinfo->parent->next;
814
                else
815
                        mimeinfo = procmime_mimeinfo_next(mimeinfo);
816
                if (!mimeinfo || mimeinfo->level <= level)
817
                        break;
818
        }
819
}
820

    
821
static void textview_write_error(TextView *textview, const gchar *msg)
822
{
823
        GtkTextBuffer *buffer;
824
        GtkTextIter iter;
825

    
826
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
827
        gtk_text_buffer_get_end_iter(buffer, &iter);
828
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, msg, -1,
829
                                                 "error", NULL);
830
}
831

    
832
void textview_show_error(TextView *textview)
833
{
834
        textview_set_font(textview, NULL);
835
        textview_clear(textview);
836
        textview_write_error(textview, _("This message can't be displayed.\n"));
837
}
838

    
839
static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
840
                                FILE *fp, const gchar *charset)
841
{
842
        FILE *tmpfp;
843
        gchar buf[BUFFSIZE];
844
        CodeConverter *conv;
845

    
846
        conv = conv_code_converter_new(charset, NULL);
847

    
848
        tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
849
        if (tmpfp) {
850
                if (mimeinfo->mime_type == MIME_TEXT_HTML &&
851
                    prefs_common.render_html)
852
                        textview_show_html(textview, tmpfp, conv);
853
                else
854
                        while (fgets(buf, sizeof(buf), tmpfp) != NULL)
855
                                textview_write_line(textview, buf, conv);
856
                fclose(tmpfp);
857
        } else {
858
                textview_write_error
859
                        (textview,
860
                         _("The body text couldn't be displayed because "
861
                           "writing to temporary file failed.\n"));
862
        }
863

    
864
        conv_code_converter_destroy(conv);
865
}
866

    
867
static void textview_show_html(TextView *textview, FILE *fp,
868
                               CodeConverter *conv)
869
{
870
        HTMLParser *parser;
871
        const gchar *str;
872

    
873
        parser = html_parser_new(fp, conv);
874
        g_return_if_fail(parser != NULL);
875

    
876
        while ((str = html_parse(parser)) != NULL) {
877
                if (parser->href != NULL)
878
                        textview_write_link(textview, str, parser->href, NULL);
879
                else
880
                        textview_write_line(textview, str, NULL);
881
        }
882
        textview_write_line(textview, "\n", NULL);
883

    
884
        html_parser_destroy(parser);
885
}
886

    
887
/* get_uri_part() - retrieves a URI starting from scanpos.
888
                    Returns TRUE if succesful */
889
static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
890
                             const gchar **bp, const gchar **ep)
891
{
892
        const gchar *ep_;
893

    
894
        g_return_val_if_fail(start != NULL, FALSE);
895
        g_return_val_if_fail(scanpos != NULL, FALSE);
896
        g_return_val_if_fail(bp != NULL, FALSE);
897
        g_return_val_if_fail(ep != NULL, FALSE);
898

    
899
        *bp = scanpos;
900

    
901
        /* find end point of URI */
902
        for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
903
                if (!g_ascii_isgraph(*ep_) ||
904
                    !isascii(*(const guchar *)ep_) ||
905
                    strchr("()<>{}[]\"", *ep_))
906
                        break;
907
        }
908

    
909
        /* no punctuation at end of string */
910

    
911
        /* FIXME: this stripping of trailing punctuations may bite with other URIs.
912
         * should pass some URI type to this function and decide on that whether
913
         * to perform punctuation stripping */
914

    
915
#define IS_REAL_PUNCT(ch)        (g_ascii_ispunct(ch) && !strchr("/?=", ch)) 
916

    
917
        for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
918
                ;
919

    
920
#undef IS_REAL_PUNCT
921

    
922
        *ep = ep_;
923

    
924
        return TRUE;                
925
}
926

    
927
static gchar *make_uri_string(const gchar *bp, const gchar *ep)
928
{
929
        return g_strndup(bp, ep - bp);
930
}
931

    
932
static gchar *make_http_uri_string(const gchar *bp, const gchar *ep)
933
{
934
        gchar *tmp;
935
        gchar *result;
936

    
937
        tmp = g_strndup(bp, ep - bp);
938
        result = g_strconcat("http://", tmp, NULL);
939
        g_free(tmp);
940

    
941
        return result;
942
}
943

    
944
/* valid mail address characters */
945
#define IS_RFC822_CHAR(ch) \
946
        (isascii(ch) && \
947
         (ch) > 32   && \
948
         (ch) != 127 && \
949
         !g_ascii_isspace(ch) && \
950
         !strchr("(),;<>\"", (ch)))
951

    
952
/* alphabet and number within 7bit ASCII */
953
#define IS_ASCII_ALNUM(ch)        (isascii(ch) && g_ascii_isalnum(ch))
954

    
955
/* get_email_part() - retrieves an email address. Returns TRUE if succesful */
956
static gboolean get_email_part(const gchar *start, const gchar *scanpos,
957
                               const gchar **bp, const gchar **ep)
958
{
959
        /* more complex than the uri part because we need to scan back and forward starting from
960
         * the scan position. */
961
        gboolean result = FALSE;
962
        const gchar *bp_;
963
        const gchar *ep_;
964

    
965
        g_return_val_if_fail(start != NULL, FALSE);
966
        g_return_val_if_fail(scanpos != NULL, FALSE);
967
        g_return_val_if_fail(bp != NULL, FALSE);
968
        g_return_val_if_fail(ep != NULL, FALSE);
969

    
970
        /* scan start of address */
971
        for (bp_ = scanpos - 1;
972
             bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
973
                ;
974

    
975
        /* TODO: should start with an alnum? */
976
        bp_++;
977
        for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
978
                ;
979

    
980
        if (bp_ != scanpos) {
981
                /* scan end of address */
982
                for (ep_ = scanpos + 1;
983
                     *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
984
                        ;
985

    
986
                /* TODO: really should terminate with an alnum? */
987
                for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
988
                     --ep_)
989
                        ;
990
                ep_++;
991

    
992
                if (ep_ > scanpos + 1) {
993
                        *ep = ep_;
994
                        *bp = bp_;
995
                        result = TRUE;
996
                }
997
        }
998

    
999
        return result;
1000
}
1001

    
1002
#undef IS_ASCII_ALNUM
1003
#undef IS_RFC822_CHAR
1004

    
1005
static gchar *make_email_string(const gchar *bp, const gchar *ep)
1006
{
1007
        /* returns a mailto: URI; mailto: is also used to detect the
1008
         * uri type later on in the button_pressed signal handler */
1009
        gchar *tmp, *enc;
1010
        gchar *result;
1011

    
1012
        tmp = g_strndup(bp, ep - bp);
1013
        enc = uriencode_for_mailto(tmp);
1014
        result = g_strconcat("mailto:", enc, NULL);
1015
        g_free(enc);
1016
        g_free(tmp);
1017

    
1018
        return result;
1019
}
1020

    
1021
#define ADD_TXT_POS(bp_, ep_, pti_) \
1022
        if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1023
                last = last->next; \
1024
                last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1025
                last->next = NULL; \
1026
        } else { \
1027
                g_warning("alloc error scanning URIs\n"); \
1028
                gtk_text_buffer_insert_with_tags_by_name \
1029
                        (buffer, &iter, linebuf, -1, fg_tag, NULL); \
1030
                return; \
1031
        }
1032

    
1033
/* textview_make_clickable_parts() - colorizes clickable parts */
1034
static void textview_make_clickable_parts(TextView *textview,
1035
                                          const gchar *fg_tag,
1036
                                          const gchar *uri_tag,
1037
                                          const gchar *linebuf)
1038
{
1039
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1040
        GtkTextBuffer *buffer;
1041
        GtkTextIter iter;
1042

    
1043
        /* parse table - in order of priority */
1044
        struct table {
1045
                const gchar *needle; /* token */
1046

    
1047
                /* token search function */
1048
                gchar    *(*search)        (const gchar *haystack,
1049
                                         const gchar *needle);
1050
                /* part parsing function */
1051
                gboolean  (*parse)        (const gchar *start,
1052
                                         const gchar *scanpos,
1053
                                         const gchar **bp_,
1054
                                         const gchar **ep_);
1055
                /* part to URI function */
1056
                gchar    *(*build_uri)        (const gchar *bp,
1057
                                         const gchar *ep);
1058
        };
1059

    
1060
        static struct table parser[] = {
1061
                {"http://",  strcasestr, get_uri_part,   make_uri_string},
1062
                {"https://", strcasestr, get_uri_part,   make_uri_string},
1063
                {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
1064
                {"www.",     strcasestr, get_uri_part,   make_http_uri_string},
1065
                {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
1066
                {"@",        strcasestr, get_email_part, make_email_string}
1067
        };
1068
        const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1069

    
1070
        /* flags for search optimization */
1071
        gboolean do_search[] = {TRUE, TRUE, TRUE, TRUE, TRUE, TRUE};
1072

    
1073
        gint  n;
1074
        const gchar *walk, *bp, *ep;
1075

    
1076
        struct txtpos {
1077
                const gchar        *bp, *ep;        /* text position */
1078
                gint                 pti;                /* index in parse table */
1079
                struct txtpos        *next;                /* next */
1080
        } head = {NULL, NULL, 0,  NULL}, *last = &head;
1081

    
1082
        buffer = gtk_text_view_get_buffer(text);
1083
        gtk_text_buffer_get_end_iter(buffer, &iter);
1084

    
1085
        /* parse for clickable parts, and build a list of begin and
1086
           end positions  */
1087
        for (walk = linebuf, n = 0;;) {
1088
                gint last_index = PARSE_ELEMS;
1089
                const gchar *scanpos = NULL;
1090

    
1091
                /* FIXME: this looks phony. scanning for anything in the
1092
                   parse table */
1093
                for (n = 0; n < PARSE_ELEMS; n++) {
1094
                        const gchar *tmp;
1095

    
1096
                        if (do_search[n]) {
1097
                                tmp = parser[n].search(walk, parser[n].needle);
1098
                                if (tmp) {
1099
                                        if (scanpos == NULL || tmp < scanpos) {
1100
                                                scanpos = tmp;
1101
                                                last_index = n;
1102
                                        }
1103
                                } else
1104
                                        do_search[n] = FALSE;
1105
                        }
1106
                }
1107

    
1108
                if (scanpos) {
1109
                        /* check if URI can be parsed */
1110
                        if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1111
                            && (ep - bp - 1) > strlen(parser[last_index].needle)) {
1112
                                        ADD_TXT_POS(bp, ep, last_index);
1113
                                        walk = ep;
1114
                        } else
1115
                                walk = scanpos +
1116
                                        strlen(parser[last_index].needle);
1117
                } else
1118
                        break;
1119
        }
1120

    
1121
        /* colorize this line */
1122
        if (head.next) {
1123
                const gchar *normal_text = linebuf;
1124

    
1125
                /* insert URIs */
1126
                for (last = head.next; last != NULL;
1127
                     normal_text = last->ep, last = last->next) {
1128
                        RemoteURI *uri;
1129

    
1130
                        uri = g_new(RemoteURI, 1);
1131
                        if (last->bp - normal_text > 0)
1132
                                gtk_text_buffer_insert_with_tags_by_name
1133
                                        (buffer, &iter,
1134
                                         normal_text,
1135
                                         last->bp - normal_text,
1136
                                         fg_tag, NULL);
1137
                        uri->uri = parser[last->pti].build_uri(last->bp,
1138
                                                               last->ep);
1139
                        uri->filename = NULL;
1140
                        uri->start = gtk_text_iter_get_offset(&iter);
1141
                        gtk_text_buffer_insert_with_tags_by_name
1142
                                (buffer, &iter, last->bp, last->ep - last->bp,
1143
                                 uri_tag, fg_tag, NULL);
1144
                        uri->end = gtk_text_iter_get_offset(&iter);
1145
                        textview->uri_list =
1146
                                g_slist_append(textview->uri_list, uri);
1147
                }
1148

    
1149
                if (*normal_text)
1150
                        gtkut_text_buffer_insert_with_tag_by_name
1151
                                (buffer, &iter, normal_text, -1, fg_tag);
1152
        } else {
1153
                gtkut_text_buffer_insert_with_tag_by_name
1154
                        (buffer, &iter, linebuf, -1, fg_tag);
1155
        }
1156
}
1157

    
1158
#undef ADD_TXT_POS
1159

    
1160
static void textview_write_line(TextView *textview, const gchar *str,
1161
                                CodeConverter *conv)
1162
{
1163
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1164
        GtkTextBuffer *buffer;
1165
        GtkTextIter iter;
1166
        gchar *buf;
1167
        gchar *fg_color = NULL;
1168
        gint quotelevel = -1;
1169
        gchar quote_tag_str[10];
1170

    
1171
        buffer = gtk_text_view_get_buffer(text);
1172
        gtk_text_buffer_get_end_iter(buffer, &iter);
1173

    
1174
        if (conv) {
1175
                buf = conv_convert(conv, str);
1176
                if (!buf)
1177
                        buf = conv_utf8todisp(str, NULL);
1178
        } else
1179
                buf = g_strdup(str);
1180

    
1181
        strcrchomp(buf);
1182
        //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1183

    
1184
        /* change color of quotation
1185
           >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1186
           Up to 3 levels of quotations are detected, and each
1187
           level is colored using a different color. */
1188
        if (prefs_common.enable_color && strchr(buf, '>')) {
1189
                quotelevel = get_quote_level(buf);
1190

    
1191
                /* set up the correct foreground color */
1192
                if (quotelevel > 2) {
1193
                        /* recycle colors */
1194
                        if (prefs_common.recycle_quote_colors)
1195
                                quotelevel %= 3;
1196
                        else
1197
                                quotelevel = 2;
1198
                }
1199
        }
1200

    
1201
        if (quotelevel != -1) {
1202
                g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1203
                           "quote%d", quotelevel);
1204
                fg_color = quote_tag_str;
1205
        }
1206

    
1207
        if (prefs_common.enable_color)
1208
                textview_make_clickable_parts(textview, fg_color, "link", buf);
1209
        else
1210
                textview_make_clickable_parts(textview, fg_color, NULL, buf);
1211

    
1212
        g_free(buf);
1213
}
1214

    
1215
static void textview_write_link(TextView *textview, const gchar *str,
1216
                                const gchar *uri, CodeConverter *conv)
1217
{
1218
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1219
        GtkTextBuffer *buffer;
1220
        GtkTextIter iter;
1221
        gchar *buf;
1222
        gchar *bufp;
1223
        RemoteURI *r_uri;
1224

    
1225
        if (!str || *str == '\0')
1226
                return;
1227
        if (!uri)
1228
                return;
1229

    
1230
        buffer = gtk_text_view_get_buffer(text);
1231
        gtk_text_buffer_get_end_iter(buffer, &iter);
1232

    
1233
        if (conv) {
1234
                buf = conv_convert(conv, str);
1235
                if (!buf)
1236
                        buf = conv_utf8todisp(str, NULL);
1237
        } else
1238
                buf = g_strdup(str);
1239

    
1240
        if (g_utf8_validate(buf, -1, NULL) == FALSE) {
1241
                g_free(buf);
1242
                return;
1243
        }
1244

    
1245
        strcrchomp(buf);
1246

    
1247
        for (bufp = buf; *bufp != '\0'; bufp = g_utf8_next_char(bufp)) {
1248
                gunichar ch;
1249

    
1250
                ch = g_utf8_get_char(bufp);
1251
                if (!g_unichar_isspace(ch))
1252
                        break;
1253
        }
1254
        if (bufp > buf)
1255
                gtk_text_buffer_insert(buffer, &iter, buf, bufp - buf);
1256

    
1257
        r_uri = g_new(RemoteURI, 1);
1258
        r_uri->uri = g_strstrip(g_strdup(uri));
1259
        r_uri->filename = NULL;
1260
        r_uri->start = gtk_text_iter_get_offset(&iter);
1261
        gtk_text_buffer_insert_with_tags_by_name
1262
                (buffer, &iter, bufp, -1, "link", NULL);
1263
        r_uri->end = gtk_text_iter_get_offset(&iter);
1264
        textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1265

    
1266
        g_free(buf);
1267
}
1268

    
1269
void textview_clear(TextView *textview)
1270
{
1271
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1272
        GtkTextBuffer *buffer;
1273

    
1274
        buffer = gtk_text_view_get_buffer(text);
1275
        gtk_text_buffer_set_text(buffer, "", -1);
1276

    
1277
        /* workaround for the assertion failure in
1278
           gtk_text_view_validate_onscreen() */
1279
        text->vadjustment->value = 0.0;
1280

    
1281
        STATUSBAR_POP(textview);
1282
        textview_uri_list_remove_all(textview->uri_list);
1283
        textview->uri_list = NULL;
1284

    
1285
        textview->body_pos = 0;
1286
}
1287

    
1288
void textview_destroy(TextView *textview)
1289
{
1290
        GtkTextBuffer *buffer;
1291
        GtkClipboard *clipboard;
1292

    
1293
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1294
        clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1295
        gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
1296

    
1297
        textview_uri_list_remove_all(textview->uri_list);
1298
        textview->uri_list = NULL;
1299
        g_free(textview);
1300
}
1301

    
1302
void textview_set_all_headers(TextView *textview, gboolean all_headers)
1303
{
1304
        textview->show_all_headers = all_headers;
1305
}
1306

    
1307
void textview_set_font(TextView *textview, const gchar *codeset)
1308
{
1309
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1310

    
1311
        if (prefs_common.textfont) {
1312
                PangoFontDescription *font_desc;
1313
                font_desc = pango_font_description_from_string
1314
                        (prefs_common.textfont);
1315
                if (font_desc) {
1316
                        gtk_widget_modify_font(textview->text, font_desc);
1317
                        pango_font_description_free(font_desc);
1318
                }
1319
        }
1320

    
1321
        gtk_text_view_set_pixels_above_lines(text, prefs_common.line_space / 2);
1322
        gtk_text_view_set_pixels_below_lines(text, prefs_common.line_space / 2);
1323
}
1324

    
1325
void textview_set_position(TextView *textview, gint pos)
1326
{
1327
        GtkTextBuffer *buffer;
1328
        GtkTextIter iter;
1329

    
1330
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1331
        gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1332
        gtk_text_buffer_place_cursor(buffer, &iter);
1333
}
1334

    
1335
static GPtrArray *textview_scan_header(TextView *textview, FILE *fp,
1336
                                       const gchar *encoding)
1337
{
1338
        g_return_val_if_fail(fp != NULL, NULL);
1339

    
1340
        if (textview->show_all_headers)
1341
                return procheader_get_header_array_asis(fp, encoding);
1342

    
1343
        if (!prefs_common.display_header) {
1344
                gchar buf[BUFFSIZE];
1345

    
1346
                while (fgets(buf, sizeof(buf), fp) != NULL)
1347
                        if (buf[0] == '\r' || buf[0] == '\n') break;
1348
                return NULL;
1349
        }
1350

    
1351
        return procheader_get_header_array_for_display(fp, encoding);
1352
}
1353

    
1354
static void textview_show_header(TextView *textview, GPtrArray *headers)
1355
{
1356
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1357
        GtkTextBuffer *buffer;
1358
        GtkTextIter iter;
1359
        Header *header;
1360
        gint i;
1361

    
1362
        g_return_if_fail(headers != NULL);
1363

    
1364
        buffer = gtk_text_view_get_buffer(text);
1365
        gtk_text_buffer_get_end_iter(buffer, &iter);
1366

    
1367
        for (i = 0; i < headers->len; i++) {
1368
                header = g_ptr_array_index(headers, i);
1369
                g_return_if_fail(header->name != NULL);
1370

    
1371
                gtk_text_buffer_insert_with_tags_by_name
1372
                        (buffer, &iter, header->name, -1,
1373
                         "header_title", "header", NULL);
1374
                gtk_text_buffer_insert_with_tags_by_name
1375
                        (buffer, &iter, ":", 1,
1376
                         "header_title", "header", NULL);
1377

    
1378
                if (!g_ascii_strcasecmp(header->name, "Subject") ||
1379
                    !g_ascii_strcasecmp(header->name, "From")    ||
1380
                    !g_ascii_strcasecmp(header->name, "To")      ||
1381
                    !g_ascii_strcasecmp(header->name, "Cc"))
1382
                        unfold_line(header->body);
1383

    
1384
                if (prefs_common.enable_color &&
1385
                    (!strncmp(header->name, "X-Mailer", 8) ||
1386
                     !strncmp(header->name, "X-Newsreader", 12)) &&
1387
                    strstr(header->body, "Sylpheed") != NULL) {
1388
                        gtk_text_buffer_insert_with_tags_by_name
1389
                                (buffer, &iter, header->body, -1,
1390
                                 "header", "emphasis", NULL);
1391
                } else if (prefs_common.enable_color) {
1392
                        textview_make_clickable_parts
1393
                                (textview, "header", "link", header->body);
1394
                } else {
1395
                        textview_make_clickable_parts
1396
                                (textview, "header", NULL, header->body);
1397
                }
1398
                gtk_text_buffer_get_end_iter(buffer, &iter);
1399
                gtk_text_buffer_insert_with_tags_by_name
1400
                        (buffer, &iter, "\n", 1, "header", NULL);
1401
        }
1402
}
1403

    
1404
gboolean textview_search_string(TextView *textview, const gchar *str,
1405
                                gboolean case_sens)
1406
{
1407
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1408
        GtkTextBuffer *buffer;
1409
        GtkTextIter iter, match_pos;
1410
        GtkTextMark *mark;
1411
        gint len;
1412

    
1413
        g_return_val_if_fail(str != NULL, FALSE);
1414

    
1415
        buffer = gtk_text_view_get_buffer(text);
1416

    
1417
        len = g_utf8_strlen(str, -1);
1418
        g_return_val_if_fail(len >= 0, FALSE);
1419

    
1420
        gtk_text_buffer_get_selection_bounds(buffer, NULL, &iter);
1421

    
1422
        if (gtkut_text_buffer_find(buffer, &iter, str, case_sens,
1423
                                   &match_pos)) {
1424
                GtkTextIter end = match_pos;
1425

    
1426
                gtk_text_iter_forward_chars(&end, len);
1427
                /* place "insert" at the last character */
1428
                gtk_text_buffer_select_range(buffer, &end, &match_pos);
1429
                mark = gtk_text_buffer_get_insert(buffer);
1430
                gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1431
                return TRUE;
1432
        }
1433

    
1434
        return FALSE;
1435
}
1436

    
1437
gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1438
                                         gboolean case_sens)
1439
{
1440
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1441
        GtkTextBuffer *buffer;
1442
        GtkTextIter iter, match_pos;
1443
        GtkTextMark *mark;
1444
        gint len;
1445

    
1446
        g_return_val_if_fail(str != NULL, FALSE);
1447

    
1448
        buffer = gtk_text_view_get_buffer(text);
1449

    
1450
        len = g_utf8_strlen(str, -1);
1451
        g_return_val_if_fail(len >= 0, FALSE);
1452

    
1453
        gtk_text_buffer_get_selection_bounds(buffer, &iter, NULL);
1454

    
1455
        if (gtkut_text_buffer_find_backward(buffer, &iter, str, case_sens,
1456
                                            &match_pos)) {
1457
                GtkTextIter end = match_pos;
1458

    
1459
                gtk_text_iter_forward_chars(&end, len);
1460
                gtk_text_buffer_select_range(buffer, &match_pos, &end);
1461
                mark = gtk_text_buffer_get_insert(buffer);
1462
                gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1463
                return TRUE;
1464
        }
1465

    
1466
        return FALSE;
1467
}
1468

    
1469
void textview_scroll_one_line(TextView *textview, gboolean up)
1470
{
1471
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1472
        GtkAdjustment *vadj = text->vadjustment;
1473
        gfloat upper;
1474

    
1475
        if (prefs_common.enable_smooth_scroll) {
1476
                textview_smooth_scroll_one_line(textview, up);
1477
                return;
1478
        }
1479

    
1480
        if (!up) {
1481
                upper = vadj->upper - vadj->page_size;
1482
                if (vadj->value < upper) {
1483
                        vadj->value += vadj->step_increment;
1484
                        vadj->value = MIN(vadj->value, upper);
1485
                        g_signal_emit_by_name(G_OBJECT(vadj),
1486
                                              "value_changed", 0);
1487
                }
1488
        } else {
1489
                if (vadj->value > 0.0) {
1490
                        vadj->value -= vadj->step_increment;
1491
                        vadj->value = MAX(vadj->value, 0.0);
1492
                        g_signal_emit_by_name(G_OBJECT(vadj),
1493
                                              "value_changed", 0);
1494
                }
1495
        }
1496
}
1497

    
1498
gboolean textview_scroll_page(TextView *textview, gboolean up)
1499
{
1500
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1501
        GtkAdjustment *vadj = text->vadjustment;
1502
        gfloat upper;
1503
        gfloat page_incr;
1504

    
1505
        if (prefs_common.enable_smooth_scroll)
1506
                return textview_smooth_scroll_page(textview, up);
1507

    
1508
        if (prefs_common.scroll_halfpage)
1509
                page_incr = vadj->page_increment / 2;
1510
        else
1511
                page_incr = vadj->page_increment;
1512

    
1513
        if (!up) {
1514
                upper = vadj->upper - vadj->page_size;
1515
                if (vadj->value < upper) {
1516
                        vadj->value += page_incr;
1517
                        vadj->value = MIN(vadj->value, upper);
1518
                        g_signal_emit_by_name(G_OBJECT(vadj),
1519
                                              "value_changed", 0);
1520
                } else
1521
                        return FALSE;
1522
        } else {
1523
                if (vadj->value > 0.0) {
1524
                        vadj->value -= page_incr;
1525
                        vadj->value = MAX(vadj->value, 0.0);
1526
                        g_signal_emit_by_name(G_OBJECT(vadj),
1527
                                              "value_changed", 0);
1528
                } else
1529
                        return FALSE;
1530
        }
1531

    
1532
        return TRUE;
1533
}
1534

    
1535
static void textview_smooth_scroll_do(TextView *textview,
1536
                                      gfloat old_value, gfloat last_value,
1537
                                      gint step)
1538
{
1539
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1540
        GtkAdjustment *vadj = text->vadjustment;
1541
        gint change_value;
1542
        gboolean up;
1543
        gint i;
1544

    
1545
        if (old_value < last_value) {
1546
                change_value = last_value - old_value;
1547
                up = FALSE;
1548
        } else {
1549
                change_value = old_value - last_value;
1550
                up = TRUE;
1551
        }
1552

    
1553
        /* gdk_key_repeat_disable(); */
1554

    
1555
        g_signal_handlers_block_by_func(vadj, textview_adj_value_changed,
1556
                                        textview);
1557

    
1558
        for (i = step; i <= change_value; i += step) {
1559
                vadj->value = old_value + (up ? -i : i);
1560
                g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1561
        }
1562

    
1563
        g_signal_handlers_unblock_by_func(vadj, textview_adj_value_changed,
1564
                                          textview);
1565

    
1566
        vadj->value = last_value;
1567
        g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1568

    
1569
        /* gdk_key_repeat_restore(); */
1570

    
1571
        gtk_widget_queue_draw(GTK_WIDGET(text));
1572
}
1573

    
1574
static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1575
{
1576
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1577
        GtkAdjustment *vadj = text->vadjustment;
1578
        gfloat upper;
1579
        gfloat old_value;
1580
        gfloat last_value;
1581

    
1582
        if (!up) {
1583
                upper = vadj->upper - vadj->page_size;
1584
                if (vadj->value < upper) {
1585
                        old_value = vadj->value;
1586
                        last_value = vadj->value + vadj->step_increment;
1587
                        last_value = MIN(last_value, upper);
1588

    
1589
                        textview_smooth_scroll_do(textview, old_value,
1590
                                                  last_value,
1591
                                                  prefs_common.scroll_step);
1592
                }
1593
        } else {
1594
                if (vadj->value > 0.0) {
1595
                        old_value = vadj->value;
1596
                        last_value = vadj->value - vadj->step_increment;
1597
                        last_value = MAX(last_value, 0.0);
1598

    
1599
                        textview_smooth_scroll_do(textview, old_value,
1600
                                                  last_value,
1601
                                                  prefs_common.scroll_step);
1602
                }
1603
        }
1604
}
1605

    
1606
static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1607
{
1608
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1609
        GtkAdjustment *vadj = text->vadjustment;
1610
        gfloat upper;
1611
        gfloat page_incr;
1612
        gfloat old_value;
1613
        gfloat last_value;
1614

    
1615
        if (prefs_common.scroll_halfpage)
1616
                page_incr = vadj->page_increment / 2;
1617
        else
1618
                page_incr = vadj->page_increment;
1619

    
1620
        if (!up) {
1621
                upper = vadj->upper - vadj->page_size;
1622
                if (vadj->value < upper) {
1623
                        old_value = vadj->value;
1624
                        last_value = vadj->value + page_incr;
1625
                        last_value = MIN(last_value, upper);
1626

    
1627
                        textview_smooth_scroll_do(textview, old_value,
1628
                                                  last_value,
1629
                                                  prefs_common.scroll_step);
1630
                } else
1631
                        return FALSE;
1632
        } else {
1633
                if (vadj->value > 0.0) {
1634
                        old_value = vadj->value;
1635
                        last_value = vadj->value - page_incr;
1636
                        last_value = MAX(last_value, 0.0);
1637

    
1638
                        textview_smooth_scroll_do(textview, old_value,
1639
                                                  last_value,
1640
                                                  prefs_common.scroll_step);
1641
                } else
1642
                        return FALSE;
1643
        }
1644

    
1645
        return TRUE;
1646
}
1647

    
1648
#define KEY_PRESS_EVENT_STOP() \
1649
        g_signal_stop_emission_by_name(G_OBJECT(widget), "key-press-event");
1650

    
1651
static gboolean textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1652
                                     TextView *textview)
1653
{
1654
        SummaryView *summaryview = NULL;
1655
        MessageView *messageview = textview->messageview;
1656

    
1657
        if (!event) return FALSE;
1658
        if (messageview->mainwin)
1659
                summaryview = messageview->mainwin->summaryview;
1660

    
1661
        switch (event->keyval) {
1662
        case GDK_Tab:
1663
        case GDK_Home:
1664
        case GDK_Left:
1665
        case GDK_Up:
1666
        case GDK_Right:
1667
        case GDK_Down:
1668
        case GDK_Page_Up:
1669
        case GDK_Page_Down:
1670
        case GDK_End:
1671
        case GDK_Control_L:
1672
        case GDK_Control_R:
1673
        case GDK_KP_Tab:
1674
        case GDK_KP_Home:
1675
        case GDK_KP_Left:
1676
        case GDK_KP_Up:
1677
        case GDK_KP_Right:
1678
        case GDK_KP_Down:
1679
        case GDK_KP_Page_Up:
1680
        case GDK_KP_Page_Down:
1681
        case GDK_KP_End:
1682
                break;
1683
        case GDK_space:
1684
        case GDK_KP_Space:
1685
                if (summaryview)
1686
                        summary_pass_key_press_event(summaryview, event);
1687
                else
1688
                        textview_scroll_page
1689
                                (textview,
1690
                                 (event->state &
1691
                                  (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1692
                break;
1693
        case GDK_BackSpace:
1694
                textview_scroll_page(textview, TRUE);
1695
                break;
1696
        case GDK_Return:
1697
        case GDK_KP_Enter:
1698
                textview_scroll_one_line
1699
                        (textview, (event->state &
1700
                                    (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1701
                break;
1702
        case GDK_Delete:
1703
        case GDK_KP_Delete:
1704
                if (summaryview)
1705
                        summary_pass_key_press_event(summaryview, event);
1706
                break;
1707
        case GDK_Escape:
1708
                if (summaryview && textview == messageview->textview)
1709
                        gtk_widget_grab_focus(summaryview->treeview);
1710
                else if (messageview->type == MVIEW_MIME &&
1711
                         textview == messageview->mimeview->textview)
1712
                        gtk_widget_grab_focus(messageview->mimeview->treeview);
1713
                break;
1714
        case GDK_n:
1715
        case GDK_N:
1716
        case GDK_p:
1717
        case GDK_P:
1718
        case GDK_y:
1719
        case GDK_t:
1720
        case GDK_l:
1721
                if (messageview->type == MVIEW_MIME &&
1722
                    textview == messageview->mimeview->textview) {
1723
                        KEY_PRESS_EVENT_STOP();
1724
                        mimeview_pass_key_press_event(messageview->mimeview,
1725
                                                      event);
1726
                        break;
1727
                }
1728
                /* fall through */
1729
        default:
1730
                if (summaryview &&
1731
                    event->window != messageview->mainwin->window->window) {
1732
                        GdkEventKey tmpev = *event;
1733

    
1734
                        tmpev.window = messageview->mainwin->window->window;
1735
                        KEY_PRESS_EVENT_STOP();
1736
                        gtk_widget_event(messageview->mainwin->window,
1737
                                         (GdkEvent *)&tmpev);
1738
                }
1739
                break;
1740
        }
1741

    
1742
        return FALSE;
1743
}
1744

    
1745
static gboolean textview_get_link_tag_bounds(TextView *textview,
1746
                                             GtkTextIter *iter,
1747
                                             GtkTextIter *start,
1748
                                             GtkTextIter *end)
1749
{
1750
        GSList *tags, *cur;
1751
        gboolean on_link = FALSE;
1752

    
1753
        tags = gtk_text_iter_get_tags(iter);
1754
        *start = *end = *iter;
1755

    
1756
        for (cur = tags; cur != NULL; cur = cur->next) {
1757
                GtkTextTag *tag = cur->data;
1758
                gchar *tag_name;
1759

    
1760
                g_object_get(G_OBJECT(tag), "name", &tag_name, NULL);
1761
                if (tag_name && !strcmp(tag_name, "link")) {
1762
                        if (!gtk_text_iter_begins_tag(start, tag))
1763
                                gtk_text_iter_backward_to_tag_toggle
1764
                                        (start, tag);
1765
                        if (!gtk_text_iter_ends_tag(end, tag))
1766
                                gtk_text_iter_forward_to_tag_toggle(end, tag);
1767
                        on_link = TRUE;
1768
                        g_free(tag_name);
1769
                        break;
1770
                }
1771
                if (tag_name)
1772
                        g_free(tag_name);
1773
        }
1774

    
1775
        if (tags)
1776
                g_slist_free(tags);
1777

    
1778
        return on_link;
1779
}
1780

    
1781
static RemoteURI *textview_get_uri(TextView *textview, GtkTextIter *start,
1782
                                   GtkTextIter *end)
1783
{
1784
        gint start_pos, end_pos;
1785
        GSList *cur;
1786
        RemoteURI *uri = NULL;
1787

    
1788
        start_pos = gtk_text_iter_get_offset(start);
1789
        end_pos = gtk_text_iter_get_offset(end);
1790

    
1791
        for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1792
                RemoteURI *uri_ = (RemoteURI *)cur->data;
1793

    
1794
                if (start_pos == uri_->start && end_pos == uri_->end) {
1795
                        uri = uri_;
1796
                        break;
1797
                }
1798
        }
1799

    
1800
        return uri;
1801
}
1802

    
1803
static gboolean textview_event_after(GtkWidget *widget, GdkEvent *event,
1804
                                     TextView *textview)
1805
{
1806
        GdkEventButton *bevent = (GdkEventButton *)event;
1807
        GtkTextBuffer *buffer;
1808
        GtkTextIter iter, start, end;
1809
        gint x, y;
1810
        gboolean on_link;
1811
        RemoteURI *uri;
1812

    
1813
        if (event->type != GDK_BUTTON_RELEASE)
1814
                return FALSE;
1815
        if (bevent->button != 1 && bevent->button != 2)
1816
                return FALSE;
1817

    
1818
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
1819

    
1820
        /* don't follow a link if the user has selected something */
1821
        gtk_text_buffer_get_selection_bounds(buffer, &start, &end);
1822
        if (!gtk_text_iter_equal(&start, &end))
1823
                return FALSE;
1824

    
1825
        gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget),
1826
                                              GTK_TEXT_WINDOW_WIDGET,
1827
                                              bevent->x, bevent->y, &x, &y);
1828
        gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &iter, x, y);
1829
        on_link = textview_get_link_tag_bounds(textview, &iter, &start, &end);
1830
        if (!on_link)
1831
                return FALSE;
1832

    
1833
        uri = textview_get_uri(textview, &start, &end);
1834
        if (!uri)
1835
                return FALSE;
1836

    
1837
        if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
1838
                PrefsAccount *ac = NULL;
1839
                MsgInfo *msginfo = textview->messageview->msginfo;
1840

    
1841
                if (msginfo && msginfo->folder)
1842
                        ac = account_find_from_item(msginfo->folder);
1843
                if (ac && ac->protocol == A_NNTP)
1844
                        ac = NULL;
1845
                compose_new(ac, msginfo->folder, uri->uri + 7, NULL);
1846
        } else if (textview_uri_security_check(textview, uri) == TRUE)
1847
                open_uri(uri->uri, prefs_common.uri_cmd);
1848

    
1849
        return FALSE;
1850
}
1851

    
1852
static void textview_show_uri(TextView *textview, GtkTextIter *start,
1853
                              GtkTextIter *end)
1854
{
1855
        RemoteURI *uri;
1856

    
1857
        STATUSBAR_POP(textview);
1858
        uri = textview_get_uri(textview, start, end);
1859
        if (uri) {
1860
                STATUSBAR_PUSH(textview, uri->uri);
1861
        }
1862
}
1863

    
1864
static void textview_set_cursor(TextView *textview, GtkTextView *text,
1865
                                gint x, gint y)
1866
{
1867
        GtkTextBuffer *buffer;
1868
        GtkTextIter iter;
1869
        GtkTextIter start, end;
1870
        GtkTextMark *start_mark, *end_mark;
1871
        gboolean on_link = FALSE;
1872

    
1873
        buffer = gtk_text_view_get_buffer(text);
1874
        gtk_text_view_get_iter_at_location(text, &iter, x, y);
1875
        on_link = textview_get_link_tag_bounds(textview, &iter, &start, &end);
1876

    
1877
        start_mark = gtk_text_buffer_get_mark(buffer, "hover-link-start");
1878
        end_mark = gtk_text_buffer_get_mark(buffer, "hover-link-end");
1879
        if (start_mark) {
1880
                GtkTextIter prev_start, prev_end;
1881

    
1882
                gtk_text_buffer_get_iter_at_mark(buffer, &prev_start,
1883
                                                 start_mark);
1884
                gtk_text_buffer_get_iter_at_mark(buffer, &prev_end, end_mark);
1885
                if (on_link) {
1886
                        if (gtk_text_iter_equal(&prev_start, &start))
1887
                                return;
1888
                }
1889

    
1890
                gtk_text_buffer_get_iter_at_mark(buffer, &prev_start,
1891
                                                 start_mark);
1892
                gtk_text_buffer_get_iter_at_mark(buffer, &prev_end, end_mark);
1893
                gtk_text_buffer_remove_tag_by_name(buffer, "hover-link",
1894
                                                   &prev_start, &prev_end);
1895
                gtk_text_buffer_delete_mark(buffer, start_mark);
1896
                gtk_text_buffer_delete_mark(buffer, end_mark);
1897
        } else {
1898
                if (!on_link)
1899
                        return;
1900
        }
1901

    
1902
        if (on_link) {
1903
                gtk_text_buffer_create_mark
1904
                        (buffer, "hover-link-start", &start, FALSE);
1905
                gtk_text_buffer_create_mark
1906
                        (buffer, "hover-link-end", &end, FALSE);
1907
                gtk_text_buffer_apply_tag_by_name
1908
                        (buffer, "hover-link", &start, &end);
1909
                gdk_window_set_cursor
1910
                        (gtk_text_view_get_window(text, GTK_TEXT_WINDOW_TEXT),
1911
                         hand_cursor);
1912
                textview_show_uri(textview, &start, &end);
1913
        } else {
1914
                gdk_window_set_cursor
1915
                        (gtk_text_view_get_window(text, GTK_TEXT_WINDOW_TEXT),
1916
                         regular_cursor);
1917
                STATUSBAR_POP(textview);
1918
        }
1919
}
1920

    
1921
static gboolean textview_motion_notify(GtkWidget *widget,
1922
                                       GdkEventMotion *event,
1923
                                       TextView *textview)
1924
{
1925
        gint x, y;
1926

    
1927
        gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget),
1928
                                              GTK_TEXT_WINDOW_WIDGET,
1929
                                              event->x, event->y, &x, &y);
1930
        textview_set_cursor(textview, GTK_TEXT_VIEW(widget), x, y);
1931
        gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1932

    
1933
        return FALSE;
1934
}
1935

    
1936
static gboolean textview_leave_notify(GtkWidget *widget,
1937
                                      GdkEventCrossing *event,
1938
                                      TextView *textview)
1939
{
1940
        textview_set_cursor(textview, GTK_TEXT_VIEW(widget), 0, 0);
1941

    
1942
        return FALSE;
1943
}
1944

    
1945
static gboolean textview_visibility_notify(GtkWidget *widget,
1946
                                           GdkEventVisibility *event,
1947
                                           TextView *textview)
1948
{
1949
        gint wx, wy, bx, by;
1950
        GdkWindow *window;
1951

    
1952
        window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1953
                                          GTK_TEXT_WINDOW_TEXT);
1954

    
1955
        /* check if the event occurred for the text window part */
1956
        if (window != event->window)
1957
                return FALSE;
1958

    
1959
        gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1960
        gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget),
1961
                                              GTK_TEXT_WINDOW_WIDGET,
1962
                                              wx, wy, &bx, &by);
1963
        textview_set_cursor(textview, GTK_TEXT_VIEW(widget), bx, by);
1964

    
1965
        return FALSE;
1966
}
1967

    
1968
#define ADD_MENU(label, func)                                                \
1969
{                                                                        \
1970
        menuitem = gtk_menu_item_new_with_mnemonic(label);                \
1971
        g_signal_connect(menuitem, "activate", G_CALLBACK(func), uri);        \
1972
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);                \
1973
        g_object_set_data(G_OBJECT(menuitem), "textview", textview);        \
1974
        gtk_widget_show(menuitem);                                        \
1975
}
1976

    
1977
#define ADD_MENU_SEPARATOR()                                                \
1978
{                                                                        \
1979
        menuitem = gtk_menu_item_new();                                        \
1980
        gtk_widget_set_sensitive(menuitem, FALSE);                        \
1981
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);                \
1982
        gtk_widget_show(menuitem);                                        \
1983
}
1984

    
1985
static void textview_populate_popup(GtkWidget *widget, GtkMenu *menu,
1986
                                    TextView *textview)
1987
{
1988
        gint px, py, x, y;
1989
        GtkWidget *separator, *menuitem;
1990
        GtkTextBuffer *buffer;
1991
        GtkTextIter iter, start, end;
1992
        gboolean on_link;
1993
        RemoteURI *uri;
1994
        GdkPixbuf *pixbuf;
1995
        gchar *selected_text;
1996

    
1997
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
1998

    
1999
        gdk_window_get_pointer(widget->window, &px, &py, NULL);
2000
        gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget),
2001
                                              GTK_TEXT_WINDOW_WIDGET,
2002
                                              px, py, &x, &y);
2003
        gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &iter, x, y);
2004
        if ((pixbuf = gtk_text_iter_get_pixbuf(&iter)) != NULL) {
2005
                start = end = iter;
2006
                gtk_text_iter_forward_char(&end);
2007
                uri = textview_get_uri(textview, &start, &end);
2008

    
2009
                separator = gtk_separator_menu_item_new();
2010
                gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator);
2011
                gtk_widget_show(separator);
2012

    
2013
                ADD_MENU(_("Sa_ve this image as..."),
2014
                         textview_popup_menu_activate_image_cb);
2015
        }
2016

    
2017
        selected_text = gtkut_text_view_get_selection(GTK_TEXT_VIEW(widget));
2018

    
2019
        uri = NULL;
2020
        on_link = textview_get_link_tag_bounds(textview, &iter, &start, &end);
2021
        if (!on_link)
2022
                goto finish;
2023

    
2024
        uri = textview_get_uri(textview, &start, &end);
2025
        if (!uri)
2026
                goto finish;
2027

    
2028
        separator = gtk_separator_menu_item_new();
2029
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator);
2030
        gtk_widget_show(separator);
2031

    
2032
        if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2033
                ADD_MENU(_("Compose _new message"),
2034
                         textview_popup_menu_activate_open_uri_cb);
2035
                ADD_MENU(_("R_eply to this address"),
2036
                         textview_popup_menu_activate_reply_cb);
2037
                ADD_MENU_SEPARATOR();
2038
                ADD_MENU(_("Add to address _book..."),
2039
                         textview_popup_menu_activate_add_address_cb);
2040
                ADD_MENU(_("Copy this add_ress"),
2041
                         textview_popup_menu_activate_copy_cb);
2042
        } else {
2043
                ADD_MENU(_("_Open with Web browser"),
2044
                         textview_popup_menu_activate_open_uri_cb);
2045
                ADD_MENU(_("Copy this _link"),
2046
                         textview_popup_menu_activate_copy_cb);
2047
        }
2048

    
2049
finish:
2050
        syl_plugin_signal_emit("textview-menu-popup", menu,
2051
                               GTK_TEXT_VIEW(widget),
2052
                               uri ? uri->uri : NULL,
2053
                               selected_text,
2054
                               textview->messageview->msginfo);
2055
        g_free(selected_text);
2056
}
2057

    
2058
static void textview_popup_menu_activate_open_uri_cb(GtkMenuItem *menuitem,
2059
                                                     gpointer data)
2060
{
2061
        RemoteURI *uri = (RemoteURI *)data;
2062
        TextView *textview;
2063

    
2064
        g_return_if_fail(uri != NULL);
2065

    
2066
        if (!uri->uri)
2067
                return;
2068

    
2069
        textview = g_object_get_data(G_OBJECT(menuitem), "textview");
2070
        g_return_if_fail(textview != NULL);
2071

    
2072
        if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2073
                PrefsAccount *ac = NULL;
2074
                MsgInfo *msginfo = textview->messageview->msginfo;
2075

    
2076
                if (msginfo && msginfo->folder)
2077
                        ac = account_find_from_item(msginfo->folder);
2078
                if (ac && ac->protocol == A_NNTP)
2079
                        ac = NULL;
2080
                compose_new(ac, msginfo->folder, uri->uri + 7, NULL);
2081
        } else if (textview_uri_security_check(textview, uri) == TRUE)
2082
                open_uri(uri->uri, prefs_common.uri_cmd);
2083
}
2084

    
2085
static void textview_popup_menu_activate_reply_cb(GtkMenuItem *menuitem,
2086
                                                  gpointer data)
2087
{
2088
        RemoteURI *uri = (RemoteURI *)data;
2089
        TextView *textview;
2090

    
2091
        g_return_if_fail(uri != NULL);
2092

    
2093
        if (!uri->uri)
2094
                return;
2095

    
2096
        textview = g_object_get_data(G_OBJECT(menuitem), "textview");
2097
        g_return_if_fail(textview != NULL);
2098

    
2099
        if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2100
                MsgInfo *msginfo = textview->messageview->msginfo;
2101
                ComposeMode mode = COMPOSE_REPLY;
2102
                gchar *text;
2103
                GList *list;
2104
                Compose *compose;
2105

    
2106
                g_return_if_fail(msginfo != NULL);
2107

    
2108
                if (prefs_common.reply_with_quote)
2109
                        mode |= COMPOSE_WITH_QUOTE;
2110

    
2111
                text = gtkut_text_view_get_selection
2112
                        (GTK_TEXT_VIEW(textview->text));
2113
                if (text && *text == '\0') {
2114
                        g_free(text);
2115
                        text = NULL;
2116
                }
2117

    
2118
                compose_reply(msginfo, msginfo->folder, mode, text);
2119
                list = compose_get_compose_list();
2120
                list = g_list_last(list);
2121
                if (list && list->data) {
2122
                        compose = (Compose *)list->data;
2123
                        compose_block_modified(compose);
2124
                        compose_entry_set(compose, uri->uri + 7,
2125
                                          COMPOSE_ENTRY_TO);
2126
                        compose_unblock_modified(compose);
2127
                }
2128
                g_free(text);
2129
        }
2130
}
2131

    
2132
static void textview_popup_menu_activate_add_address_cb(GtkMenuItem *menuitem,
2133
                                                        gpointer data)
2134
{
2135
        RemoteURI *uri = (RemoteURI *)data;
2136
        gchar *addr;
2137

    
2138
        g_return_if_fail(uri != NULL);
2139

    
2140
        if (!uri->uri)
2141
                return;
2142

    
2143
        if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2144
                addr = g_malloc(strlen(uri->uri + 7) + 1);
2145
                decode_uri(addr, uri->uri + 7);
2146
        } else
2147
                addr = g_strdup(uri->uri);
2148

    
2149
        addressbook_add_contact(addr, addr, NULL);
2150

    
2151
        g_free(addr);
2152
}
2153

    
2154
static void textview_popup_menu_activate_copy_cb(GtkMenuItem *menuitem,
2155
                                                 gpointer data)
2156
{
2157
        RemoteURI *uri = (RemoteURI *)data;
2158
        gchar *uri_string;
2159
        GtkClipboard *clipboard;
2160

    
2161
        g_return_if_fail(uri != NULL);
2162

    
2163
        if (!uri->uri)
2164
                return;
2165

    
2166
        if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2167
                uri_string = g_malloc(strlen(uri->uri + 7) + 1);
2168
                decode_uri(uri_string, uri->uri + 7);
2169
        } else
2170
                uri_string = g_strdup(uri->uri);
2171

    
2172
        clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2173
        gtk_clipboard_set_text(clipboard, uri_string, -1);
2174
        clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
2175
        gtk_clipboard_set_text(clipboard, uri_string, -1);
2176

    
2177
        g_free(uri_string);
2178
}
2179

    
2180
static void textview_popup_menu_activate_image_cb(GtkMenuItem *menuitem,
2181
                                                  gpointer data)
2182
{
2183
        RemoteURI *uri = (RemoteURI *)data;
2184
        gchar *src;
2185
        gchar *dest;
2186
        gchar *filename;
2187

    
2188
        g_return_if_fail(uri != NULL);
2189

    
2190
        if (!uri->uri)
2191
                return;
2192

    
2193
        src = g_filename_from_uri(uri->uri, NULL, NULL);
2194
        g_return_if_fail(src != NULL);
2195

    
2196
        filename = conv_filename_to_utf8(uri->filename);
2197
        dest = filesel_save_as(filename);
2198
        if (dest) {
2199
                copy_file(src, dest, FALSE);
2200
                g_free(dest);
2201
        }
2202
        g_free(filename);
2203
        g_free(src);
2204
}
2205

    
2206
static void textview_adj_value_changed(GtkAdjustment *adj, gpointer data)
2207
{
2208
        TextView *textview = (TextView *)data;
2209
        GtkTextBuffer *buffer;
2210

    
2211
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2212
        if (gtk_text_buffer_get_selection_bounds(buffer, NULL, NULL))
2213
                return;
2214
        gtk_text_view_place_cursor_onscreen(GTK_TEXT_VIEW(textview->text));
2215
}
2216

    
2217
static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2218
{
2219
        GtkTextBuffer *buffer;
2220
        GtkTextIter start_iter, end_iter;
2221
        gchar *visible_str;
2222
        gboolean retval = TRUE;
2223

    
2224
        if (is_uri_string(uri->uri) == FALSE)
2225
                return TRUE;
2226

    
2227
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2228
        gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, uri->start);
2229
        gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, uri->end);
2230
        visible_str = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter,
2231
                                               FALSE);
2232
        if (visible_str == NULL)
2233
                return TRUE;
2234

    
2235
        if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2236
                gchar *uri_path;
2237
                gchar *visible_uri_path;
2238

    
2239
                uri_path = get_uri_path(uri->uri);
2240
                visible_uri_path = get_uri_path(visible_str);
2241
                if (path_cmp(uri_path, visible_uri_path) != 0)
2242
                        retval = FALSE;
2243
        }
2244

    
2245
        if (retval == FALSE) {
2246
                gchar *msg;
2247
                AlertValue aval;
2248

    
2249
                msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2250
                                        "the apparent URL (%s).\n"
2251
                                        "\n"
2252
                                        "Open it anyway?"),
2253
                                      uri->uri, visible_str);
2254
                aval = alertpanel_full(_("Fake URL warning"), msg,
2255
                                       ALERT_WARNING, G_ALERTDEFAULT, FALSE,
2256
                                       GTK_STOCK_YES, GTK_STOCK_NO, NULL);
2257
                g_free(msg);
2258
                if (aval == G_ALERTDEFAULT)
2259
                        retval = TRUE;
2260
        }
2261

    
2262
        g_free(visible_str);
2263

    
2264
        return retval;
2265
}
2266

    
2267
static void textview_uri_list_remove_all(GSList *uri_list)
2268
{
2269
        GSList *cur;
2270

    
2271
        for (cur = uri_list; cur != NULL; cur = cur->next) {
2272
                RemoteURI *uri = (RemoteURI *)cur->data;
2273

    
2274
                if (uri) {
2275
                        g_free(uri->uri);
2276
                        g_free(uri->filename);
2277
                        g_free(uri);
2278
                }
2279
        }
2280

    
2281
        g_slist_free(uri_list);
2282
}