Statistics
| Revision:

root / src / textview.c @ 6

History | View | Annotate | Download (42 kB)

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