Statistics
| Revision:

root / src / textview.c @ 92

History | View | Annotate | Download (43 kB)

1
/*
2
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3
 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
 */
19
20
#ifdef HAVE_CONFIG_H
21
#  include "config.h"
22
#endif
23
24
#include "defs.h"
25
26
#include <glib.h>
27
#include <glib/gi18n.h>
28
#include <gdk/gdk.h>
29
#include <gdk/gdkkeysyms.h>
30
#include <gtk/gtkvbox.h>
31
#include <gtk/gtkscrolledwindow.h>
32
#include <gtk/gtksignal.h>
33
#include <stdio.h>
34
#include <ctype.h>
35
#include <string.h>
36
#include <stdlib.h>
37
38
#include "main.h"
39
#include "summaryview.h"
40
#include "procheader.h"
41
#include "prefs_common.h"
42
#include "codeconv.h"
43
#include "statusbar.h"
44
#include "utils.h"
45
#include "gtkutils.h"
46
#include "procmime.h"
47
#include "account.h"
48
#include "html.h"
49
#include "compose.h"
50
#include "displayheader.h"
51
#include "alertpanel.h"
52
53
typedef struct _RemoteURI        RemoteURI;
54
55
struct _RemoteURI
56
{
57
        gchar *uri;
58
59
        guint start;
60
        guint end;
61
};
62
63
static GdkColor quote_colors[3] = {
64
        {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
65
        {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
66
        {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
67
};
68
69
static GdkColor uri_color = {
70
        (gulong)0,
71
        (gushort)0,
72
        (gushort)0,
73
        (gushort)0
74
};
75
76
static GdkColor emphasis_color = {
77
        (gulong)0,
78
        (gushort)0,
79
        (gushort)0,
80
        (gushort)0xcfff
81
};
82
83
#if 0
84
static GdkColor error_color = {
85
        (gulong)0,
86
        (gushort)0xefff,
87
        (gushort)0,
88
        (gushort)0
89
};
90
#endif
91
92
#if USE_GPGME
93
static GdkColor good_sig_color = {
94
        (gulong)0,
95
        (gushort)0,
96
        (gushort)0xbfff,
97
        (gushort)0
98
};
99
100
static GdkColor untrusted_sig_color = {
101
        (gulong)0,
102
        (gushort)0xefff,
103
        (gushort)0,
104
        (gushort)0
105
};
106
107
static GdkColor nocheck_sig_color = {
108
        (gulong)0,
109
        (gushort)0,
110
        (gushort)0,
111
        (gushort)0xcfff
112
};
113
114
static GdkColor bad_sig_color = {
115
        (gulong)0,
116
        (gushort)0xefff,
117
        (gushort)0,
118
        (gushort)0
119
};
120
#endif
121
122
#define STATUSBAR_PUSH(textview, str)                                            \
123
{                                                                            \
124
        gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
125
                           textview->messageview->statusbar_cid, str);            \
126
}
127
128
#define STATUSBAR_POP(textview)                                                   \
129
{                                                                           \
130
        gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
131
                          textview->messageview->statusbar_cid);           \
132
}
133
134
static void textview_add_part                (TextView        *textview,
135
                                         MimeInfo        *mimeinfo,
136
                                         FILE                *fp);
137
static void textview_add_parts                (TextView        *textview,
138
                                         MimeInfo        *mimeinfo,
139
                                         FILE                *fp);
140
static void textview_write_body                (TextView        *textview,
141
                                         MimeInfo        *mimeinfo,
142
                                         FILE                *fp,
143
                                         const gchar        *charset);
144
static void textview_show_html                (TextView        *textview,
145
                                         FILE                *fp,
146
                                         CodeConverter        *conv);
147
148
static void textview_write_line                (TextView        *textview,
149
                                         const gchar        *str,
150
                                         CodeConverter        *conv);
151
static void textview_write_link                (TextView        *textview,
152
                                         const gchar        *str,
153
                                         const gchar        *uri,
154
                                         CodeConverter        *conv);
155
156
static GPtrArray *textview_scan_header        (TextView        *textview,
157
                                         FILE                *fp);
158
static void textview_show_header        (TextView        *textview,
159
                                         GPtrArray        *headers);
160
161
static gboolean textview_key_pressed                (GtkWidget        *widget,
162
                                                 GdkEventKey        *event,
163
                                                 TextView        *textview);
164
static gboolean textview_uri_button_pressed        (GtkTextTag        *tag,
165
                                                 GObject        *obj,
166
                                                 GdkEvent        *event,
167
                                                 GtkTextIter        *iter,
168
                                                 TextView        *textview);
169
170
static void textview_smooth_scroll_do                (TextView        *textview,
171
                                                 gfloat                 old_value,
172
                                                 gfloat                 last_value,
173
                                                 gint                 step);
174
static void textview_smooth_scroll_one_line        (TextView        *textview,
175
                                                 gboolean         up);
176
static gboolean textview_smooth_scroll_page        (TextView        *textview,
177
                                                 gboolean         up);
178
179
static gboolean textview_uri_security_check        (TextView        *textview,
180
                                                 RemoteURI        *uri);
181
static void textview_uri_list_remove_all        (GSList                *uri_list);
182
183
184
TextView *textview_create(void)
185
{
186
        TextView *textview;
187
        GtkWidget *vbox;
188
        GtkWidget *scrolledwin;
189
        GtkWidget *text;
190
        GtkTextBuffer *buffer;
191
        GtkClipboard *clipboard;
192
193
        debug_print(_("Creating text view...\n"));
194
        textview = g_new0(TextView, 1);
195
196
        scrolledwin = gtk_scrolled_window_new(NULL, NULL);
197
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
198
                                       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
199
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
200
                                            GTK_SHADOW_IN);
201
        gtk_widget_set_size_request
202
                (scrolledwin, prefs_common.mainview_width, -1);
203
204
        text = gtk_text_view_new();
205
        gtk_widget_show(text);
206
        gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
207
        gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
208
        gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
209
        gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
210
211
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
212
        clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
213
        gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
214
215
        gtk_widget_ref(scrolledwin);
216
217
        gtk_container_add(GTK_CONTAINER(scrolledwin), text);
218
219
        g_signal_connect(G_OBJECT(text), "key_press_event",
220
                         G_CALLBACK(textview_key_pressed), textview);
221
222
        gtk_widget_show(scrolledwin);
223
224
        vbox = gtk_vbox_new(FALSE, 0);
225
        gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
226
227
        gtk_widget_show(vbox);
228
229
        textview->vbox             = vbox;
230
        textview->scrolledwin      = scrolledwin;
231
        textview->text             = text;
232
        textview->uri_list         = NULL;
233
        textview->body_pos         = 0;
234
        textview->show_all_headers = FALSE;
235
236
        return textview;
237
}
238
239
static void textview_create_tags(GtkTextView *text, TextView *textview)
240
{
241
        GtkTextBuffer *buffer;
242
        GtkTextTag *tag;
243
        static PangoFontDescription *font_desc, *bold_font_desc;
244
245
        if (!font_desc) {
246
                font_desc = gtkut_get_default_font_desc();
247
                bold_font_desc = pango_font_description_copy(font_desc);
248
                pango_font_description_set_weight
249
                        (bold_font_desc, PANGO_WEIGHT_BOLD);
250
        }
251
252
        buffer = gtk_text_view_get_buffer(text);
253
254
        gtk_text_buffer_create_tag(buffer, "header",
255
                                   "pixels-above-lines", 0,
256
                                   "pixels-above-lines-set", TRUE,
257
                                   "pixels-below-lines", 0,
258
                                   "pixels-below-lines-set", TRUE,
259
                                   "font-desc", font_desc,
260
                                   NULL);
261
        gtk_text_buffer_create_tag(buffer, "header_title",
262
                                   "font-desc", bold_font_desc,
263
                                   NULL);
264
        gtk_text_buffer_create_tag(buffer, "quote0",
265
                                   "foreground-gdk", &quote_colors[0],
266
                                   NULL);
267
        gtk_text_buffer_create_tag(buffer, "quote1",
268
                                   "foreground-gdk", &quote_colors[1],
269
                                   NULL);
270
        gtk_text_buffer_create_tag(buffer, "quote2",
271
                                   "foreground-gdk", &quote_colors[2],
272
                                   NULL);
273
        gtk_text_buffer_create_tag(buffer, "emphasis",
274
                                   "foreground-gdk", &emphasis_color,
275
                                   NULL);
276
        tag = gtk_text_buffer_create_tag(buffer, "link",
277
                                         "foreground-gdk", &uri_color,
278
                                         NULL);
279
#if USE_GPGME
280
        gtk_text_buffer_create_tag(buffer, "good-signature",
281
                                   "foreground-gdk", &good_sig_color,
282
                                   NULL);
283
        gtk_text_buffer_create_tag(buffer, "untrusted-signature",
284
                                   "foreground-gdk", &untrusted_sig_color,
285
                                   NULL);
286
        gtk_text_buffer_create_tag(buffer, "bad-signature",
287
                                   "foreground-gdk", &bad_sig_color,
288
                                   NULL);
289
        gtk_text_buffer_create_tag(buffer, "nocheck-signature",
290
                                   "foreground-gdk", &nocheck_sig_color,
291
                                   NULL);
292
#endif /* USE_GPGME */
293
294
        g_signal_connect(G_OBJECT(tag), "event",
295
                         G_CALLBACK(textview_uri_button_pressed), textview);
296
}
297
298
void textview_init(TextView *textview)
299
{
300
        textview_update_message_colors();
301
        textview_set_all_headers(textview, FALSE);
302
        textview_set_font(textview, NULL);
303
        textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
304
}
305
306
void textview_update_message_colors(void)
307
{
308
        GdkColor black = {0, 0, 0, 0};
309
310
        if (prefs_common.enable_color) {
311
                /* grab the quote colors, converting from an int to a GdkColor */
312
                gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
313
                                               &quote_colors[0]);
314
                gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
315
                                               &quote_colors[1]);
316
                gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
317
                                               &quote_colors[2]);
318
                gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
319
                                               &uri_color);
320
        } else {
321
                quote_colors[0] = quote_colors[1] = quote_colors[2] = 
322
                        uri_color = emphasis_color = black;
323
        }
324
}
325
326
void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
327
                           const gchar *file)
328
{
329
        FILE *fp;
330
        const gchar *charset = NULL;
331
        GPtrArray *headers = NULL;
332
333
        if ((fp = fopen(file, "rb")) == NULL) {
334
                FILE_OP_ERROR(file, "fopen");
335
                return;
336
        }
337
338
        if (textview->messageview->forced_charset)
339
                charset = textview->messageview->forced_charset;
340
        else if (prefs_common.force_charset)
341
                charset = prefs_common.force_charset;
342
        else if (mimeinfo->charset)
343
                charset = mimeinfo->charset;
344
345
        textview_set_font(textview, charset);
346
        textview_clear(textview);
347
348
        if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) perror("fseek");
349
        headers = textview_scan_header(textview, fp);
350
        if (headers) {
351
                GtkTextView *text = GTK_TEXT_VIEW(textview->text);
352
                GtkTextBuffer *buffer;
353
                GtkTextIter iter;
354
355
                textview_show_header(textview, headers);
356
                procheader_header_array_destroy(headers);
357
358
                buffer = gtk_text_view_get_buffer(text);
359
                gtk_text_buffer_get_end_iter(buffer, &iter);
360
                textview->body_pos = gtk_text_iter_get_offset(&iter);
361
        }
362
363
        textview_add_parts(textview, mimeinfo, fp);
364
365
        fclose(fp);
366
367
        textview_set_position(textview, 0);
368
}
369
370
void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
371
{
372
        gchar buf[BUFFSIZE];
373
        const gchar *boundary = NULL;
374
        gint boundary_len = 0;
375
        const gchar *charset = NULL;
376
        GPtrArray *headers = NULL;
377
        gboolean is_rfc822_part = FALSE;
378
379
        g_return_if_fail(mimeinfo != NULL);
380
        g_return_if_fail(fp != NULL);
381
382
        if (mimeinfo->mime_type == MIME_MULTIPART) {
383
                textview_clear(textview);
384
                textview_add_parts(textview, mimeinfo, fp);
385
                return;
386
        }
387
388
        if (mimeinfo->parent && mimeinfo->parent->boundary) {
389
                boundary = mimeinfo->parent->boundary;
390
                boundary_len = strlen(boundary);
391
        }
392
393
        if (!boundary && mimeinfo->mime_type == MIME_TEXT) {
394
                if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0)
395
                        perror("fseek");
396
                headers = textview_scan_header(textview, fp);
397
        } else {
398
                if (mimeinfo->mime_type == MIME_TEXT && mimeinfo->parent) {
399
                        glong fpos;
400
                        MimeInfo *parent = mimeinfo->parent;
401
402
                        while (parent->parent) {
403
                                if (parent->main &&
404
                                    parent->main->mime_type ==
405
                                        MIME_MESSAGE_RFC822)
406
                                        break;
407
                                parent = parent->parent;
408
                        }
409
410
                        if ((fpos = ftell(fp)) < 0)
411
                                perror("ftell");
412
                        else if (fseek(fp, parent->fpos, SEEK_SET) < 0)
413
                                perror("fseek");
414
                        else {
415
                                headers = textview_scan_header(textview, fp);
416
                                if (fseek(fp, fpos, SEEK_SET) < 0)
417
                                        perror("fseek");
418
                        }
419
                }
420
                /* skip MIME part headers */
421
                while (fgets(buf, sizeof(buf), fp) != NULL)
422
                        if (buf[0] == '\r' || buf[0] == '\n') break;
423
        }
424
425
        /* display attached RFC822 single text message */
426
        if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
427
                if (headers) procheader_header_array_destroy(headers);
428
                if (!mimeinfo->sub) {
429
                        textview_clear(textview);
430
                        return;
431
                }
432
                headers = textview_scan_header(textview, fp);
433
                mimeinfo = mimeinfo->sub;
434
                is_rfc822_part = TRUE;
435
        }
436
437
        if (textview->messageview->forced_charset)
438
                charset = textview->messageview->forced_charset;
439
        else if (prefs_common.force_charset)
440
                charset = prefs_common.force_charset;
441
        else if (mimeinfo->charset)
442
                charset = mimeinfo->charset;
443
444
        textview_set_font(textview, charset);
445
446
        textview_clear(textview);
447
448
        if (headers) {
449
                GtkTextView *text = GTK_TEXT_VIEW(textview->text);
450
                GtkTextBuffer *buffer;
451
                GtkTextIter iter;
452
453
                textview_show_header(textview, headers);
454
                procheader_header_array_destroy(headers);
455
456
                buffer = gtk_text_view_get_buffer(text);
457
                gtk_text_buffer_get_end_iter(buffer, &iter);
458
                textview->body_pos = gtk_text_iter_get_offset(&iter);
459
                if (!mimeinfo->main)
460
                        gtk_text_buffer_insert(buffer, &iter, "\n", 1);
461
        }
462
463
        if (mimeinfo->mime_type == MIME_MULTIPART || is_rfc822_part)
464
                textview_add_parts(textview, mimeinfo, fp);
465
        else
466
                textview_write_body(textview, mimeinfo, fp, charset);
467
}
468
469
static void textview_add_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
470
{
471
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
472
        GtkTextBuffer *buffer;
473
        GtkTextIter iter;
474
        gchar buf[BUFFSIZE];
475
        const gchar *boundary = NULL;
476
        gint boundary_len = 0;
477
        const gchar *charset = NULL;
478
        GPtrArray *headers = NULL;
479
480
        g_return_if_fail(mimeinfo != NULL);
481
        g_return_if_fail(fp != NULL);
482
483
        buffer = gtk_text_view_get_buffer(text);
484
        gtk_text_buffer_get_end_iter(buffer, &iter);
485
486
        if (mimeinfo->mime_type == MIME_MULTIPART) return;
487
488
        if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) {
489
                perror("fseek");
490
                return;
491
        }
492
493
        if (mimeinfo->parent && mimeinfo->parent->boundary) {
494
                boundary = mimeinfo->parent->boundary;
495
                boundary_len = strlen(boundary);
496
        }
497
498
        while (fgets(buf, sizeof(buf), fp) != NULL)
499
                if (buf[0] == '\r' || buf[0] == '\n') break;
500
501
        if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
502
                headers = textview_scan_header(textview, fp);
503
                if (headers) {
504
                        gtk_text_buffer_insert(buffer, &iter, "\n", 1);
505
                        textview_show_header(textview, headers);
506
                        procheader_header_array_destroy(headers);
507
                }
508
                return;
509
        }
510
511
#if USE_GPGME
512
        if (mimeinfo->sigstatus)
513
                g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n",
514
                           mimeinfo->content_type, mimeinfo->sigstatus);
515
        else
516
#endif
517
        if (mimeinfo->filename || mimeinfo->name)
518
                g_snprintf(buf, sizeof(buf), "\n[%s  %s (%d bytes)]\n",
519
                           mimeinfo->filename ? mimeinfo->filename :
520
                           mimeinfo->name,
521
                           mimeinfo->content_type, mimeinfo->size);
522
        else
523
                g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
524
                           mimeinfo->content_type, mimeinfo->size);
525
526
#if USE_GPGME
527
        if (mimeinfo->sigstatus) {
528
                const gchar *color;
529
                if (!strcmp(mimeinfo->sigstatus, _("Good signature")))
530
                        color = "good-signature";
531
                else if (!strcmp(mimeinfo->sigstatus, _("Valid signature (untrusted key)")))
532
                        color = "untrusted-signature";
533
                else if (!strcmp(mimeinfo->sigstatus, _("BAD signature")))
534
                        color = "bad-signature";
535
                else
536
                        color = "nocheck-signature";
537
                gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, buf, -1,
538
                                                         color, NULL);
539
        } else
540
#endif
541
        if (mimeinfo->mime_type != MIME_TEXT &&
542
            mimeinfo->mime_type != MIME_TEXT_HTML) {
543
                gtk_text_buffer_insert(buffer, &iter, buf, -1);
544
        } else {
545
                if (!mimeinfo->main &&
546
                    mimeinfo->parent &&
547
                    mimeinfo->parent->children != mimeinfo)
548
                        gtk_text_buffer_insert(buffer, &iter, buf, -1);
549
                else
550
                        gtk_text_buffer_insert(buffer, &iter, "\n", 1);
551
                if (textview->messageview->forced_charset)
552
                        charset = textview->messageview->forced_charset;
553
                else if (prefs_common.force_charset)
554
                        charset = prefs_common.force_charset;
555
                else if (mimeinfo->charset)
556
                        charset = mimeinfo->charset;
557
                textview_write_body(textview, mimeinfo, fp, charset);
558
        }
559
}
560
561
static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
562
{
563
        gint level;
564
565
        g_return_if_fail(mimeinfo != NULL);
566
        g_return_if_fail(fp != NULL);
567
568
        level = mimeinfo->level;
569
570
        for (;;) {
571
                textview_add_part(textview, mimeinfo, fp);
572
                if (mimeinfo->parent && mimeinfo->parent->content_type &&
573
                    !strcasecmp(mimeinfo->parent->content_type,
574
                                "multipart/alternative"))
575
                        mimeinfo = mimeinfo->parent->next;
576
                else
577
                        mimeinfo = procmime_mimeinfo_next(mimeinfo);
578
                if (!mimeinfo || mimeinfo->level <= level)
579
                        break;
580
        }
581
}
582
583
#define TEXT_INSERT(str) \
584
        gtk_text_buffer_insert(buffer, &iter, str, -1)
585
586
void textview_show_error(TextView *textview)
587
{
588
        GtkTextBuffer *buffer;
589
        GtkTextIter iter;
590
591
        textview_set_font(textview, NULL);
592
        textview_clear(textview);
593
594
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
595
        gtk_text_buffer_get_start_iter(buffer, &iter);
596
        TEXT_INSERT(_("This message can't be displayed.\n"));
597
}
598
599
void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
600
{
601
        GtkTextBuffer *buffer;
602
        GtkTextIter iter;
603
604
        if (!partinfo) return;
605
606
        textview_set_font(textview, NULL);
607
        textview_clear(textview);
608
609
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
610
        gtk_text_buffer_get_start_iter(buffer, &iter);
611
612
        TEXT_INSERT(_("To save this part, pop up the context menu with "));
613
        TEXT_INSERT(_("right click and select `Save as...', "));
614
        TEXT_INSERT(_("or press `y' key.\n\n"));
615
616
        TEXT_INSERT(_("To display this part as a text message, select "));
617
        TEXT_INSERT(_("`Display as text', or press `t' key.\n\n"));
618
619
        TEXT_INSERT(_("To open this part with external program, select "));
620
        TEXT_INSERT(_("`Open' or `Open with...', "));
621
        TEXT_INSERT(_("or double-click, or click the center button, "));
622
        TEXT_INSERT(_("or press `l' key."));
623
}
624
625
#if USE_GPGME
626
void textview_show_signature_part(TextView *textview, MimeInfo *partinfo)
627
{
628
        GtkTextBuffer *buffer;
629
        GtkTextIter iter;
630
631
        if (!partinfo) return;
632
633
        textview_set_font(textview, NULL);
634
        textview_clear(textview);
635
636
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
637
        gtk_text_buffer_get_start_iter(buffer, &iter);
638
639
        if (partinfo->sigstatus_full == NULL) {
640
                TEXT_INSERT(_("This signature has not been checked yet.\n"));
641
                TEXT_INSERT(_("To check it, pop up the context menu with\n"));
642
                TEXT_INSERT(_("right click and select `Check signature'.\n"));
643
        } else {
644
                TEXT_INSERT(partinfo->sigstatus_full);
645
        }
646
}
647
#endif /* USE_GPGME */
648
649
#undef TEXT_INSERT
650
651
static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
652
                                FILE *fp, const gchar *charset)
653
{
654
        FILE *tmpfp;
655
        gchar buf[BUFFSIZE];
656
        CodeConverter *conv;
657
658
        conv = conv_code_converter_new(charset);
659
660
        tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
661
        if (tmpfp) {
662
                if (mimeinfo->mime_type == MIME_TEXT_HTML)
663
                        textview_show_html(textview, tmpfp, conv);
664
                else
665
                        while (fgets(buf, sizeof(buf), tmpfp) != NULL)
666
                                textview_write_line(textview, buf, conv);
667
                fclose(tmpfp);
668
        }
669
670
        conv_code_converter_destroy(conv);
671
}
672
673
static void textview_show_html(TextView *textview, FILE *fp,
674
                               CodeConverter *conv)
675
{
676
        HTMLParser *parser;
677
        gchar *str;
678
679
        parser = html_parser_new(fp, conv);
680
        g_return_if_fail(parser != NULL);
681
682
        while ((str = html_parse(parser)) != NULL) {
683
                if (parser->href != NULL)
684
                        textview_write_link(textview, str, parser->href, NULL);
685
                else
686
                        textview_write_line(textview, str, NULL);
687
        }
688
        html_parser_destroy(parser);
689
}
690
691
/* get_uri_part() - retrieves a URI starting from scanpos.
692
                    Returns TRUE if succesful */
693
static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
694
                             const gchar **bp, const gchar **ep)
695
{
696
        const gchar *ep_;
697
698
        g_return_val_if_fail(start != NULL, FALSE);
699
        g_return_val_if_fail(scanpos != NULL, FALSE);
700
        g_return_val_if_fail(bp != NULL, FALSE);
701
        g_return_val_if_fail(ep != NULL, FALSE);
702
703
        *bp = scanpos;
704
705
        /* find end point of URI */
706
        for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
707
                if (!isgraph(*(const guchar *)ep_) ||
708
                    !isascii(*(const guchar *)ep_) ||
709
                    strchr("()<>\"", *ep_))
710
                        break;
711
        }
712
713
        /* no punctuation at end of string */
714
715
        /* FIXME: this stripping of trailing punctuations may bite with other URIs.
716
         * should pass some URI type to this function and decide on that whether
717
         * to perform punctuation stripping */
718
719
#define IS_REAL_PUNCT(ch)        (ispunct(ch) && ((ch) != '/')) 
720
721
        for (; ep_ - 1 > scanpos + 1 &&
722
               IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
723
             ep_--)
724
                ;
725
726
#undef IS_REAL_PUNCT
727
728
        *ep = ep_;
729
730
        return TRUE;                
731
}
732
733
static gchar *make_uri_string(const gchar *bp, const gchar *ep)
734
{
735
        return g_strndup(bp, ep - bp);
736
}
737
738
/* valid mail address characters */
739
#define IS_RFC822_CHAR(ch) \
740
        (isascii(ch) && \
741
         (ch) > 32   && \
742
         (ch) != 127 && \
743
         !isspace(ch) && \
744
         !strchr("(),;<>\"", (ch)))
745
746
/* alphabet and number within 7bit ASCII */
747
#define IS_ASCII_ALNUM(ch)        (isascii(ch) && isalnum(ch))
748
749
/* get_email_part() - retrieves an email address. Returns TRUE if succesful */
750
static gboolean get_email_part(const gchar *start, const gchar *scanpos,
751
                               const gchar **bp, const gchar **ep)
752
{
753
        /* more complex than the uri part because we need to scan back and forward starting from
754
         * the scan position. */
755
        gboolean result = FALSE;
756
        const gchar *bp_;
757
        const gchar *ep_;
758
759
        g_return_val_if_fail(start != NULL, FALSE);
760
        g_return_val_if_fail(scanpos != NULL, FALSE);
761
        g_return_val_if_fail(bp != NULL, FALSE);
762
        g_return_val_if_fail(ep != NULL, FALSE);
763
764
        /* scan start of address */
765
        for (bp_ = scanpos - 1;
766
             bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
767
                ;
768
769
        /* TODO: should start with an alnum? */
770
        bp_++;
771
        for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
772
                ;
773
774
        if (bp_ != scanpos) {
775
                /* scan end of address */
776
                for (ep_ = scanpos + 1;
777
                     *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
778
                        ;
779
780
                /* TODO: really should terminate with an alnum? */
781
                for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
782
                     --ep_)
783
                        ;
784
                ep_++;
785
786
                if (ep_ > scanpos + 1) {
787
                        *ep = ep_;
788
                        *bp = bp_;
789
                        result = TRUE;
790
                }
791
        }
792
793
        return result;
794
}
795
796
#undef IS_ASCII_ALNUM
797
#undef IS_RFC822_CHAR
798
799
static gchar *make_email_string(const gchar *bp, const gchar *ep)
800
{
801
        /* returns a mailto: URI; mailto: is also used to detect the
802
         * uri type later on in the button_pressed signal handler */
803
        gchar *tmp;
804
        gchar *result;
805
806
        tmp = g_strndup(bp, ep - bp);
807
        result = g_strconcat("mailto:", tmp, NULL);
808
        g_free(tmp);
809
810
        return result;
811
}
812
813
#define ADD_TXT_POS(bp_, ep_, pti_) \
814
        if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
815
                last = last->next; \
816
                last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
817
                last->next = NULL; \
818
        } else { \
819
                g_warning("alloc error scanning URIs\n"); \
820
                gtk_text_buffer_insert_with_tags_by_name \
821
                        (buffer, &iter, linebuf, -1, fg_tag, NULL); \
822
                return; \
823
        }
824
825
/* textview_make_clickable_parts() - colorizes clickable parts */
826
static void textview_make_clickable_parts(TextView *textview,
827
                                          const gchar *fg_tag,
828
                                          const gchar *uri_tag,
829
                                          const gchar *linebuf)
830
{
831
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
832
        GtkTextBuffer *buffer;
833
        GtkTextIter iter;
834
835
        /* parse table - in order of priority */
836
        struct table {
837
                const gchar *needle; /* token */
838
839
                /* token search function */
840
                gchar    *(*search)        (const gchar *haystack,
841
                                         const gchar *needle);
842
                /* part parsing function */
843
                gboolean  (*parse)        (const gchar *start,
844
                                         const gchar *scanpos,
845
                                         const gchar **bp_,
846
                                         const gchar **ep_);
847
                /* part to URI function */
848
                gchar    *(*build_uri)        (const gchar *bp,
849
                                         const gchar *ep);
850
        };
851
852
        static struct table parser[] = {
853
                {"http://",  strcasestr, get_uri_part,   make_uri_string},
854
                {"https://", strcasestr, get_uri_part,   make_uri_string},
855
                {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
856
                {"www.",     strcasestr, get_uri_part,   make_uri_string},
857
                {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
858
                {"@",        strcasestr, get_email_part, make_email_string}
859
        };
860
        const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
861
862
        gint  n;
863
        const gchar *walk, *bp, *ep;
864
865
        struct txtpos {
866
                const gchar        *bp, *ep;        /* text position */
867
                gint                 pti;                /* index in parse table */
868
                struct txtpos        *next;                /* next */
869
        } head = {NULL, NULL, 0,  NULL}, *last = &head;
870
871
        buffer = gtk_text_view_get_buffer(text);
872
        gtk_text_buffer_get_end_iter(buffer, &iter);
873
874
        /* parse for clickable parts, and build a list of begin and
875
           end positions  */
876
        for (walk = linebuf, n = 0;;) {
877
                gint last_index = PARSE_ELEMS;
878
                gchar *scanpos = NULL;
879
880
                /* FIXME: this looks phony. scanning for anything in the
881
                   parse table */
882
                for (n = 0; n < PARSE_ELEMS; n++) {
883
                        gchar *tmp;
884
885
                        tmp = parser[n].search(walk, parser[n].needle);
886
                        if (tmp) {
887
                                if (scanpos == NULL || tmp < scanpos) {
888
                                        scanpos = tmp;
889
                                        last_index = n;
890
                                }
891
                        }                                        
892
                }
893
894
                if (scanpos) {
895
                        /* check if URI can be parsed */
896
                        if (parser[last_index].parse(walk, scanpos, &bp, &ep)
897
                            && (ep - bp - 1) > strlen(parser[last_index].needle)) {
898
                                        ADD_TXT_POS(bp, ep, last_index);
899
                                        walk = ep;
900
                        } else
901
                                walk = scanpos +
902
                                        strlen(parser[last_index].needle);
903
                } else
904
                        break;
905
        }
906
907
        /* colorize this line */
908
        if (head.next) {
909
                const gchar *normal_text = linebuf;
910
911
                /* insert URIs */
912
                for (last = head.next; last != NULL;
913
                     normal_text = last->ep, last = last->next) {
914
                        RemoteURI *uri;
915
916
                        uri = g_new(RemoteURI, 1);
917
                        if (last->bp - normal_text > 0)
918
                                gtk_text_buffer_insert_with_tags_by_name
919
                                        (buffer, &iter,
920
                                         normal_text,
921
                                         last->bp - normal_text,
922
                                         fg_tag, NULL);
923
                        uri->uri = parser[last->pti].build_uri(last->bp,
924
                                                               last->ep);
925
                        uri->start = gtk_text_iter_get_offset(&iter);
926
                        gtk_text_buffer_insert_with_tags_by_name
927
                                (buffer, &iter, last->bp, last->ep - last->bp,
928
                                 uri_tag, fg_tag, NULL);
929
                        uri->end = gtk_text_iter_get_offset(&iter);
930
                        textview->uri_list =
931
                                g_slist_append(textview->uri_list, uri);
932
                }
933
934
                if (*normal_text)
935
                        gtk_text_buffer_insert_with_tags_by_name
936
                                (buffer, &iter, normal_text, -1, fg_tag, NULL);
937
        } else {
938
                gtk_text_buffer_insert_with_tags_by_name
939
                        (buffer, &iter, linebuf, -1, fg_tag, NULL);
940
        }
941
}
942
943
#undef ADD_TXT_POS
944
945
static void textview_write_line(TextView *textview, const gchar *str,
946
                                CodeConverter *conv)
947
{
948
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
949
        GtkTextBuffer *buffer;
950
        GtkTextIter iter;
951
        gchar buf[BUFFSIZE];
952
        gchar *fg_color;
953
        gint quotelevel = -1;
954
        gchar quote_tag_str[10];
955
956
        buffer = gtk_text_view_get_buffer(text);
957
        gtk_text_buffer_get_end_iter(buffer, &iter);
958
959
        if (!conv)
960
                strncpy2(buf, str, sizeof(buf));
961
        else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
962
                conv_utf8todisp(buf, sizeof(buf), str);
963
964
        strcrchomp(buf);
965
        //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
966
        fg_color = NULL;
967
968
        /* change color of quotation
969
           >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
970
           Up to 3 levels of quotations are detected, and each
971
           level is colored using a different color. */
972
        if (prefs_common.enable_color && strchr(buf, '>')) {
973
                quotelevel = get_quote_level(buf);
974
975
                /* set up the correct foreground color */
976
                if (quotelevel > 2) {
977
                        /* recycle colors */
978
                        if (prefs_common.recycle_quote_colors)
979
                                quotelevel %= 3;
980
                        else
981
                                quotelevel = 2;
982
                }
983
        }
984
985
        if (quotelevel == -1)
986
                fg_color = NULL;
987
        else {
988
                g_snprintf(quote_tag_str, sizeof(quote_tag_str),
989
                           "quote%d", quotelevel);
990
                fg_color = quote_tag_str;
991
        }
992
993
        if (prefs_common.enable_color)
994
                textview_make_clickable_parts(textview, fg_color, "link", buf);
995
        else
996
                textview_make_clickable_parts(textview, fg_color, NULL, buf);
997
}
998
999
void textview_write_link(TextView *textview, const gchar *str,
1000
                         const gchar *uri, CodeConverter *conv)
1001
{
1002
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1003
        GtkTextBuffer *buffer;
1004
        GtkTextIter iter;
1005
        gchar buf[BUFFSIZE];
1006
        gchar *bufp;
1007
        RemoteURI *r_uri;
1008
1009
        if (*str == '\0')
1010
                return;
1011
1012
        buffer = gtk_text_view_get_buffer(text);
1013
        gtk_text_buffer_get_end_iter(buffer, &iter);
1014
1015
        if (!conv)
1016
                strncpy2(buf, str, sizeof(buf));
1017
        else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1018
                conv_utf8todisp(buf, sizeof(buf), str);
1019
1020
        strcrchomp(buf);
1021
1022
        for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1023
                gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1024
1025
        r_uri = g_new(RemoteURI, 1);
1026
        r_uri->uri = g_strdup(uri);
1027
        r_uri->start = gtk_text_iter_get_offset(&iter);
1028
        gtk_text_buffer_insert_with_tags_by_name
1029
                (buffer, &iter, bufp, -1, "link", NULL);
1030
        r_uri->end = gtk_text_iter_get_offset(&iter);
1031
        textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1032
}
1033
1034
void textview_clear(TextView *textview)
1035
{
1036
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1037
        GtkTextBuffer *buffer;
1038
1039
        buffer = gtk_text_view_get_buffer(text);
1040
        gtk_text_buffer_set_text(buffer, "", -1);
1041
1042
        STATUSBAR_POP(textview);
1043
        textview_uri_list_remove_all(textview->uri_list);
1044
        textview->uri_list = NULL;
1045
1046
        textview->body_pos = 0;
1047
        //textview->cur_pos  = 0;
1048
}
1049
1050
void textview_destroy(TextView *textview)
1051
{
1052
        textview_uri_list_remove_all(textview->uri_list);
1053
        textview->uri_list = NULL;
1054
        g_free(textview);
1055
}
1056
1057
void textview_set_all_headers(TextView *textview, gboolean all_headers)
1058
{
1059
        textview->show_all_headers = all_headers;
1060
}
1061
1062
void textview_set_font(TextView *textview, const gchar *codeset)
1063
{
1064
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1065
1066
        if (prefs_common.textfont) {
1067
                PangoFontDescription *font_desc;
1068
                font_desc = pango_font_description_from_string
1069
                        (prefs_common.textfont);
1070
                if (font_desc) {
1071
                        gtk_widget_modify_font(textview->text, font_desc);
1072
                        pango_font_description_free(font_desc);
1073
                }
1074
        }
1075
1076
        gtk_text_view_set_pixels_above_lines(text, prefs_common.line_space / 2);
1077
        gtk_text_view_set_pixels_below_lines(text, prefs_common.line_space / 2);
1078
}
1079
1080
void textview_set_position(TextView *textview, gint pos)
1081
{
1082
        GtkTextBuffer *buffer;
1083
        GtkTextIter iter;
1084
1085
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1086
        gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1087
        gtk_text_buffer_place_cursor(buffer, &iter);
1088
}
1089
1090
static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1091
{
1092
        gchar buf[BUFFSIZE];
1093
        GPtrArray *headers, *sorted_headers;
1094
        GSList *disphdr_list;
1095
        Header *header;
1096
        gint i;
1097
1098
        g_return_val_if_fail(fp != NULL, NULL);
1099
1100
        if (textview->show_all_headers)
1101
                return procheader_get_header_array_asis(fp);
1102
1103
        if (!prefs_common.display_header) {
1104
                while (fgets(buf, sizeof(buf), fp) != NULL)
1105
                        if (buf[0] == '\r' || buf[0] == '\n') break;
1106
                return NULL;
1107
        }
1108
1109
        headers = procheader_get_header_array_asis(fp);
1110
1111
        sorted_headers = g_ptr_array_new();
1112
1113
        for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1114
             disphdr_list = disphdr_list->next) {
1115
                DisplayHeaderProp *dp =
1116
                        (DisplayHeaderProp *)disphdr_list->data;
1117
1118
                for (i = 0; i < headers->len; i++) {
1119
                        header = g_ptr_array_index(headers, i);
1120
1121
                        if (!g_strcasecmp(header->name, dp->name)) {
1122
                                if (dp->hidden)
1123
                                        procheader_header_free(header);
1124
                                else
1125
                                        g_ptr_array_add(sorted_headers, header);
1126
1127
                                g_ptr_array_remove_index(headers, i);
1128
                                i--;
1129
                        }
1130
                }
1131
        }
1132
1133
        if (prefs_common.show_other_header) {
1134
                for (i = 0; i < headers->len; i++) {
1135
                        header = g_ptr_array_index(headers, i);
1136
                        g_ptr_array_add(sorted_headers, header);
1137
                }
1138
                g_ptr_array_free(headers, TRUE);
1139
        } else
1140
                procheader_header_array_destroy(headers);
1141
1142
1143
        return sorted_headers;
1144
}
1145
1146
static void textview_show_header(TextView *textview, GPtrArray *headers)
1147
{
1148
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1149
        GtkTextBuffer *buffer;
1150
        GtkTextIter iter;
1151
        Header *header;
1152
        gint i;
1153
1154
        g_return_if_fail(headers != NULL);
1155
1156
        buffer = gtk_text_view_get_buffer(text);
1157
1158
        for (i = 0; i < headers->len; i++) {
1159
                header = g_ptr_array_index(headers, i);
1160
                g_return_if_fail(header->name != NULL);
1161
1162
                gtk_text_buffer_get_end_iter(buffer, &iter);
1163
                gtk_text_buffer_insert_with_tags_by_name
1164
                        (buffer, &iter, header->name, -1,
1165
                         "header_title", "header", NULL);
1166
                gtk_text_buffer_insert_with_tags_by_name
1167
                        (buffer, &iter, ":", 1,
1168
                         "header_title", "header", NULL);
1169
1170
                if (!g_strcasecmp(header->name, "Subject") ||
1171
                    !g_strcasecmp(header->name, "From")    ||
1172
                    !g_strcasecmp(header->name, "To")      ||
1173
                    !g_strcasecmp(header->name, "Cc"))
1174
                        unfold_line(header->body);
1175
1176
#if 0
1177
                if (textview->text_is_mb == TRUE)
1178
                        conv_unreadable_locale(header->body);
1179
#endif
1180
1181
                if (prefs_common.enable_color &&
1182
                    (!strncmp(header->name, "X-Mailer", 8) ||
1183
                     !strncmp(header->name, "X-Newsreader", 12)) &&
1184
                    strstr(header->body, "Sylpheed") != NULL) {
1185
                        gtk_text_buffer_get_end_iter(buffer, &iter);
1186
                        gtk_text_buffer_insert_with_tags_by_name
1187
                                (buffer, &iter, header->body, -1,
1188
                                 "header", "emphasis", NULL);
1189
                } else if (prefs_common.enable_color) {
1190
                        textview_make_clickable_parts
1191
                                (textview, "header", "link", header->body);
1192
                } else {
1193
                        textview_make_clickable_parts
1194
                                (textview, "header", NULL, header->body);
1195
                }
1196
                gtk_text_buffer_get_end_iter(buffer, &iter); //
1197
                gtk_text_buffer_insert_with_tags_by_name
1198
                        (buffer, &iter, "\n", 1, "header", NULL);
1199
        }
1200
}
1201
1202
gboolean textview_search_string(TextView *textview, const gchar *str,
1203
                                gboolean case_sens)
1204
{
1205
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1206
        GtkTextBuffer *buffer;
1207
        GtkTextIter iter, end_iter;
1208
        GtkTextMark *mark;
1209
        gint pos;
1210
        gint len;
1211
1212
        g_return_val_if_fail(str != NULL, FALSE);
1213
1214
        buffer = gtk_text_view_get_buffer(text);
1215
1216
        len = g_utf8_strlen(str, -1);
1217
        g_return_val_if_fail(len >= 0, FALSE);
1218
1219
        mark = gtk_text_buffer_get_insert(buffer);
1220
        gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1221
        pos = gtk_text_iter_get_offset(&iter);
1222
1223
        if ((pos = gtkut_text_buffer_find(buffer, pos, str, case_sens)) != -1) {
1224
                gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, pos);
1225
                gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos + len);
1226
                gtk_text_buffer_select_range(buffer, &iter, &end_iter);
1227
                gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1228
                return TRUE;
1229
        }
1230
1231
        return FALSE;
1232
}
1233
1234
gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1235
                                         gboolean case_sens)
1236
{
1237
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1238
        GtkTextBuffer *buffer;
1239
        GtkTextIter iter, end_iter;
1240
        GtkTextMark *mark;
1241
        gint pos;
1242
        gunichar *wcs;
1243
        gint len;
1244
        glong items_read = 0, items_written = 0;
1245
        GError *error = NULL;
1246
        gboolean found = FALSE;
1247
1248
        g_return_val_if_fail(str != NULL, FALSE);
1249
1250
        buffer = gtk_text_view_get_buffer(text);
1251
1252
        wcs = g_utf8_to_ucs4(str, -1, &items_read, &items_written, &error);
1253
        if (error != NULL) {
1254
                g_warning("An error occured while converting a string from UTF-8 to UCS-4: %s\n", error->message);
1255
                g_error_free(error);
1256
        }
1257
        if (!wcs || items_written <= 0) return FALSE;
1258
        len = (gint)items_written;
1259
1260
        mark = gtk_text_buffer_get_insert(buffer);
1261
        gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1262
1263
        while (gtk_text_iter_backward_char(&iter)) {
1264
                pos = gtk_text_iter_get_offset(&iter);
1265
                if (gtkut_text_buffer_match_string
1266
                        (buffer, pos, wcs, len, case_sens) == TRUE) {
1267
                        gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1268
                        gtk_text_buffer_get_iter_at_offset
1269
                                (buffer, &end_iter, pos + len);
1270
                        gtk_text_buffer_select_range(buffer, &iter, &end_iter);
1271
                        gtk_text_view_scroll_to_mark
1272
                                (text, mark, 0.0, FALSE, 0.0, 0.0);
1273
                        found = TRUE;
1274
                        break;
1275
                }
1276
        }
1277
1278
        g_free(wcs);
1279
        return found;
1280
}
1281
1282
void textview_scroll_one_line(TextView *textview, gboolean up)
1283
{
1284
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1285
        GtkAdjustment *vadj = text->vadjustment;
1286
        gfloat upper;
1287
1288
        if (prefs_common.enable_smooth_scroll) {
1289
                textview_smooth_scroll_one_line(textview, up);
1290
                return;
1291
        }
1292
1293
        if (!up) {
1294
                upper = vadj->upper - vadj->page_size;
1295
                if (vadj->value < upper) {
1296
                        vadj->value += vadj->step_increment;
1297
                        vadj->value = MIN(vadj->value, upper);
1298
                        g_signal_emit_by_name(G_OBJECT(vadj),
1299
                                              "value_changed", 0);
1300
                }
1301
        } else {
1302
                if (vadj->value > 0.0) {
1303
                        vadj->value -= vadj->step_increment;
1304
                        vadj->value = MAX(vadj->value, 0.0);
1305
                        g_signal_emit_by_name(G_OBJECT(vadj),
1306
                                              "value_changed", 0);
1307
                }
1308
        }
1309
}
1310
1311
gboolean textview_scroll_page(TextView *textview, gboolean up)
1312
{
1313
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1314
        GtkAdjustment *vadj = text->vadjustment;
1315
        gfloat upper;
1316
        gfloat page_incr;
1317
1318
        if (prefs_common.enable_smooth_scroll)
1319
                return textview_smooth_scroll_page(textview, up);
1320
1321
        if (prefs_common.scroll_halfpage)
1322
                page_incr = vadj->page_increment / 2;
1323
        else
1324
                page_incr = vadj->page_increment;
1325
1326
        if (!up) {
1327
                upper = vadj->upper - vadj->page_size;
1328
                if (vadj->value < upper) {
1329
                        vadj->value += page_incr;
1330
                        vadj->value = MIN(vadj->value, upper);
1331
                        g_signal_emit_by_name(G_OBJECT(vadj),
1332
                                              "value_changed", 0);
1333
                } else
1334
                        return FALSE;
1335
        } else {
1336
                if (vadj->value > 0.0) {
1337
                        vadj->value -= page_incr;
1338
                        vadj->value = MAX(vadj->value, 0.0);
1339
                        g_signal_emit_by_name(G_OBJECT(vadj),
1340
                                              "value_changed", 0);
1341
                } else
1342
                        return FALSE;
1343
        }
1344
1345
        return TRUE;
1346
}
1347
1348
static void textview_smooth_scroll_do(TextView *textview,
1349
                                      gfloat old_value, gfloat last_value,
1350
                                      gint step)
1351
{
1352
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1353
        GtkAdjustment *vadj = text->vadjustment;
1354
        gint change_value;
1355
        gboolean up;
1356
        gint i;
1357
1358
        if (old_value < last_value) {
1359
                change_value = last_value - old_value;
1360
                up = FALSE;
1361
        } else {
1362
                change_value = old_value - last_value;
1363
                up = TRUE;
1364
        }
1365
1366
#warning FIXME_GTK2
1367
        /* gdk_key_repeat_disable(); */
1368
1369
        for (i = step; i <= change_value; i += step) {
1370
                vadj->value = old_value + (up ? -i : i);
1371
                g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1372
        }
1373
1374
        vadj->value = last_value;
1375
        g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1376
1377
#warning FIXME_GTK2
1378
        /* gdk_key_repeat_restore(); */
1379
}
1380
1381
static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1382
{
1383
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1384
        GtkAdjustment *vadj = text->vadjustment;
1385
        gfloat upper;
1386
        gfloat old_value;
1387
        gfloat last_value;
1388
1389
        if (!up) {
1390
                upper = vadj->upper - vadj->page_size;
1391
                if (vadj->value < upper) {
1392
                        old_value = vadj->value;
1393
                        last_value = vadj->value + vadj->step_increment;
1394
                        last_value = MIN(last_value, upper);
1395
1396
                        textview_smooth_scroll_do(textview, old_value,
1397
                                                  last_value,
1398
                                                  prefs_common.scroll_step);
1399
                }
1400
        } else {
1401
                if (vadj->value > 0.0) {
1402
                        old_value = vadj->value;
1403
                        last_value = vadj->value - vadj->step_increment;
1404
                        last_value = MAX(last_value, 0.0);
1405
1406
                        textview_smooth_scroll_do(textview, old_value,
1407
                                                  last_value,
1408
                                                  prefs_common.scroll_step);
1409
                }
1410
        }
1411
}
1412
1413
static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1414
{
1415
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1416
        GtkAdjustment *vadj = text->vadjustment;
1417
        gfloat upper;
1418
        gfloat page_incr;
1419
        gfloat old_value;
1420
        gfloat last_value;
1421
1422
        if (prefs_common.scroll_halfpage)
1423
                page_incr = vadj->page_increment / 2;
1424
        else
1425
                page_incr = vadj->page_increment;
1426
1427
        if (!up) {
1428
                upper = vadj->upper - vadj->page_size;
1429
                if (vadj->value < upper) {
1430
                        old_value = vadj->value;
1431
                        last_value = vadj->value + page_incr;
1432
                        last_value = MIN(last_value, upper);
1433
1434
                        textview_smooth_scroll_do(textview, old_value,
1435
                                                  last_value,
1436
                                                  prefs_common.scroll_step);
1437
                } else
1438
                        return FALSE;
1439
        } else {
1440
                if (vadj->value > 0.0) {
1441
                        old_value = vadj->value;
1442
                        last_value = vadj->value - page_incr;
1443
                        last_value = MAX(last_value, 0.0);
1444
1445
                        textview_smooth_scroll_do(textview, old_value,
1446
                                                  last_value,
1447
                                                  prefs_common.scroll_step);
1448
                } else
1449
                        return FALSE;
1450
        }
1451
1452
        return TRUE;
1453
}
1454
1455
#warning FIXME_GTK2
1456
#if 0
1457
#define KEY_PRESS_EVENT_STOP() \
1458
        if (gtk_signal_n_emissions_by_name \
1459
                (GTK_OBJECT(widget), "key_press_event") > 0) { \
1460
                gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1461
                                             "key_press_event"); \
1462
        }
1463
#else
1464
#define KEY_PRESS_EVENT_STOP() \
1465
        g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
1466
#endif
1467
1468
static gboolean textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1469
                                     TextView *textview)
1470
{
1471
        SummaryView *summaryview = NULL;
1472
        MessageView *messageview = textview->messageview;
1473
1474
        if (!event) return FALSE;
1475
        if (messageview->mainwin)
1476
                summaryview = messageview->mainwin->summaryview;
1477
1478
        switch (event->keyval) {
1479
        case GDK_Tab:
1480
        case GDK_Home:
1481
        case GDK_Left:
1482
        case GDK_Up:
1483
        case GDK_Right:
1484
        case GDK_Down:
1485
        case GDK_Page_Up:
1486
        case GDK_Page_Down:
1487
        case GDK_End:
1488
        case GDK_Control_L:
1489
        case GDK_Control_R:
1490
                break;
1491
        case GDK_space:
1492
                if (summaryview)
1493
                        summary_pass_key_press_event(summaryview, event);
1494
                else
1495
                        textview_scroll_page
1496
                                (textview,
1497
                                 (event->state &
1498
                                  (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1499
                break;
1500
        case GDK_BackSpace:
1501
                textview_scroll_page(textview, TRUE);
1502
                break;
1503
        case GDK_Return:
1504
                textview_scroll_one_line
1505
                        (textview, (event->state &
1506
                                    (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1507
                break;
1508
        case GDK_Delete:
1509
                if (summaryview)
1510
                        summary_pass_key_press_event(summaryview, event);
1511
                break;
1512
        case GDK_n:
1513
        case GDK_N:
1514
        case GDK_p:
1515
        case GDK_P:
1516
        case GDK_y:
1517
        case GDK_t:
1518
        case GDK_l:
1519
                if (messageview->type == MVIEW_MIME &&
1520
                    textview == messageview->mimeview->textview) {
1521
                        KEY_PRESS_EVENT_STOP();
1522
                        mimeview_pass_key_press_event(messageview->mimeview,
1523
                                                      event);
1524
                        break;
1525
                }
1526
                /* fall through */
1527
        default:
1528
                if (summaryview &&
1529
                    event->window != messageview->mainwin->window->window) {
1530
                        GdkEventKey tmpev = *event;
1531
1532
                        tmpev.window = messageview->mainwin->window->window;
1533
                        KEY_PRESS_EVENT_STOP();
1534
                        gtk_widget_event(messageview->mainwin->window,
1535
                                         (GdkEvent *)&tmpev);
1536
                }
1537
                break;
1538
        }
1539
1540
        return FALSE;
1541
}
1542
1543
static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1544
                                            GdkEvent *event, GtkTextIter *iter,
1545
                                            TextView *textview)
1546
{
1547
        GtkTextIter start_iter, end_iter;
1548
        gint start_pos, end_pos;
1549
        GdkEventButton *bevent;
1550
        RemoteURI *uri = NULL;
1551
        GSList *cur;
1552
        gchar *trimmed_uri;
1553
1554
        if (!event)
1555
                return FALSE;
1556
1557
        if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS)
1558
                return FALSE;
1559
1560
        start_iter = *iter;
1561
1562
        if (!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) {
1563
                debug_print("Can't find start point.");
1564
                return FALSE;
1565
        }
1566
        start_pos = gtk_text_iter_get_offset(&start_iter);
1567
1568
        end_iter = *iter;
1569
        if (!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) {
1570
                debug_print("Can't find end");
1571
                return FALSE;
1572
        }
1573
        end_pos = gtk_text_iter_get_offset(&end_iter);
1574
1575
        for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1576
                RemoteURI *uri_ = (RemoteURI *)cur->data;
1577
1578
                if (start_pos == uri_->start && end_pos == uri_->end) {
1579
                        uri = uri_;
1580
                        break;
1581
                }
1582
        }
1583
1584
        STATUSBAR_POP(textview);
1585
1586
        if (!uri)
1587
                return FALSE;
1588
1589
        trimmed_uri = trim_string(uri->uri, 60);
1590
        STATUSBAR_PUSH(textview, trimmed_uri);
1591
        g_free(trimmed_uri);
1592
1593
        bevent = (GdkEventButton *)event;
1594
        if ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) ||
1595
             bevent->button == 2) {
1596
                if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1597
                        PrefsAccount *ac = NULL;
1598
                        MsgInfo *msginfo = textview->messageview->msginfo;
1599
1600
                        if (msginfo && msginfo->folder)
1601
                                ac = account_find_from_item(msginfo->folder);
1602
                        if (ac && ac->protocol == A_NNTP)
1603
                                ac = NULL;
1604
                        compose_new(ac, msginfo->folder, uri->uri + 7, NULL);
1605
                } else if (textview_uri_security_check(textview, uri) == TRUE) {
1606
                        open_uri(uri->uri, prefs_common.uri_cmd);
1607
                        return TRUE; //
1608
                }
1609
        }
1610
1611
        return FALSE;
1612
}
1613
1614
static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
1615
{
1616
        GtkTextBuffer *buffer;
1617
        GtkTextIter start_iter, end_iter;
1618
        gchar *visible_str;
1619
        gboolean retval = TRUE;
1620
1621
        if (is_uri_string(uri->uri) == FALSE)
1622
                return TRUE;
1623
1624
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1625
        gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, uri->start);
1626
        gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, uri->end);
1627
        visible_str = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter,
1628
                                               FALSE);
1629
        if (visible_str == NULL)
1630
                return TRUE;
1631
1632
        if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
1633
                gchar *uri_path;
1634
                gchar *visible_uri_path;
1635
1636
                uri_path = get_uri_path(uri->uri);
1637
                visible_uri_path = get_uri_path(visible_str);
1638
                if (strcmp(uri_path, visible_uri_path) != 0)
1639
                        retval = FALSE;
1640
        }
1641
1642
        if (retval == FALSE) {
1643
                gchar *msg;
1644
                AlertValue aval;
1645
1646
                msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1647
                                        "the apparent URL (%s).\n"
1648
                                        "Open it anyway?"),
1649
                                      uri->uri, visible_str);
1650
                aval = alertpanel(_("Warning"), msg,
1651
                                  GTK_STOCK_YES, GTK_STOCK_NO, NULL);
1652
                g_free(msg);
1653
                if (aval == G_ALERTDEFAULT)
1654
                        retval = TRUE;
1655
        }
1656
1657
        g_free(visible_str);
1658
1659
        return retval;
1660
}
1661
1662
static void textview_uri_list_remove_all(GSList *uri_list)
1663
{
1664
        GSList *cur;
1665
1666
        for (cur = uri_list; cur != NULL; cur = cur->next) {
1667
                if (cur->data) {
1668
                        g_free(((RemoteURI *)cur->data)->uri);
1669
                        g_free(cur->data);
1670
                }
1671
        }
1672
1673
        g_slist_free(uri_list);
1674
}