Statistics
| Revision:

root / src / mimeview.c @ 1128

History | View | Annotate | Download (32.7 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/gdkkeysyms.h>
29
#include <gtk/gtkscrolledwindow.h>
30
#include <gtk/gtktreestore.h>
31
#include <gtk/gtktreeview.h>
32
#include <gtk/gtktreeselection.h>
33
#include <gtk/gtkcellrenderertext.h>
34
#include <gtk/gtkvbox.h>
35
#include <gtk/gtkvpaned.h>
36
#include <gtk/gtksignal.h>
37
#include <gtk/gtkmenu.h>
38
#include <gtk/gtkdnd.h>
39
#include <gtk/gtkselection.h>
40
#include <gtk/gtknotebook.h>
41
#include <gtk/gtkvbbox.h>
42
#include <stdio.h>
43
#include <unistd.h>
44
45
#ifdef G_OS_WIN32
46
#  include <windows.h>
47
#endif
48
49
#include "main.h"
50
#include "mimeview.h"
51
#include "textview.h"
52
#include "imageview.h"
53
#include "procmime.h"
54
#include "summaryview.h"
55
#include "menu.h"
56
#include "filesel.h"
57
#include "alertpanel.h"
58
#include "inputdialog.h"
59
#include "utils.h"
60
#include "gtkutils.h"
61
#include "prefs_common.h"
62
#include "rfc2015.h"
63
64
enum
65
{
66
        COL_MIMETYPE,
67
        COL_SIZE,
68
        COL_NAME,
69
        COL_MIME_INFO,
70
        N_COLS
71
};
72
73
static void mimeview_set_multipart_tree                (MimeView        *mimeview,
74
                                                 MimeInfo        *mimeinfo,
75
                                                 GtkTreeIter        *parent);
76
static gboolean mimeview_append_part                (MimeView        *mimeview,
77
                                                 MimeInfo        *partinfo,
78
                                                 GtkTreeIter        *iter,
79
                                                 GtkTreeIter        *parent);
80
static void mimeview_show_message_part                (MimeView        *mimeview,
81
                                                 MimeInfo        *partinfo);
82
static void mimeview_show_image_part                (MimeView        *mimeview,
83
                                                 MimeInfo        *partinfo);
84
static void mimeview_show_mime_part                (MimeView        *mimeview,
85
                                                 MimeInfo        *partinfo);
86
#if USE_GPGME
87
static void mimeview_show_signature_part        (MimeView        *mimeview,
88
                                                 MimeInfo        *partinfo);
89
#endif
90
static void mimeview_change_view_type                (MimeView        *mimeview,
91
                                                 MimeViewType         type);
92
93
static void mimeview_selection_changed        (GtkTreeSelection        *selection,
94
                                         MimeView                *mimeview);
95
96
static gint mimeview_button_pressed        (GtkWidget        *widget,
97
                                         GdkEventButton        *event,
98
                                         MimeView        *mimeview);
99
static gint mimeview_key_pressed        (GtkWidget        *widget,
100
                                         GdkEventKey        *event,
101
                                         MimeView        *mimeview);
102
103
static void mimeview_drag_begin         (GtkWidget          *widget,
104
                                         GdkDragContext          *drag_context,
105
                                         MimeView          *mimeview);
106
static void mimeview_drag_end                 (GtkWidget          *widget,
107
                                         GdkDragContext          *drag_context,
108
                                         MimeView          *mimeview);
109
static void mimeview_drag_data_get      (GtkWidget          *widget,
110
                                         GdkDragContext   *drag_context,
111
                                         GtkSelectionData *selection_data,
112
                                         guint                   info,
113
                                         guint                   time,
114
                                         MimeView          *mimeview);
115
116
static void mimeview_display_as_text        (MimeView        *mimeview);
117
static void mimeview_launch                (MimeView        *mimeview);
118
static void mimeview_open_with                (MimeView        *mimeview);
119
static void mimeview_view_file                (const gchar        *filename,
120
                                         MimeInfo        *partinfo,
121
                                         const gchar        *cmdline);
122
#if USE_GPGME
123
static void mimeview_check_signature        (MimeView        *mimeview);
124
#endif
125
126
static GtkItemFactoryEntry mimeview_popup_entries[] =
127
{
128
        {N_("/_Open"),                  NULL, mimeview_launch,          0, NULL},
129
        {N_("/Open _with..."),          NULL, mimeview_open_with,          0, NULL},
130
        {N_("/_Display as text"), NULL, mimeview_display_as_text, 0, NULL},
131
        {N_("/_Save as..."),          NULL, mimeview_save_as,          0, NULL},
132
        {N_("/Save _all..."),          NULL, mimeview_save_all,          0, NULL}
133
#if USE_GPGME
134
        ,
135
        {N_("/_Check signature"), NULL, mimeview_check_signature, 0, NULL}
136
#endif
137
};
138
139
static GtkTargetEntry mimeview_mime_types[] =
140
{
141
        {"text/uri-list", 0, 0}
142
};
143
144
MimeView *mimeview_create(void)
145
{
146
        MimeView *mimeview;
147
148
        GtkWidget *paned;
149
        GtkWidget *scrolledwin;
150
        GtkWidget *treeview;
151
        GtkTreeStore *store;
152
        GtkTreeSelection *selection;
153
        GtkTreeViewColumn *column;
154
        GtkCellRenderer *renderer;
155
        GtkWidget *mime_vbox;
156
        GtkWidget *popupmenu;
157
        GtkItemFactory *popupfactory;
158
        gint n_entries;
159
160
        debug_print(_("Creating MIME view...\n"));
161
        mimeview = g_new0(MimeView, 1);
162
163
        scrolledwin = gtk_scrolled_window_new(NULL, NULL);
164
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
165
                                       GTK_POLICY_AUTOMATIC,
166
                                       GTK_POLICY_ALWAYS);
167
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
168
                                            GTK_SHADOW_IN);
169
        gtk_widget_set_size_request(scrolledwin, -1, 80);
170
171
        store = gtk_tree_store_new(N_COLS, G_TYPE_STRING, G_TYPE_STRING,
172
                                   G_TYPE_STRING, G_TYPE_POINTER);
173
174
        treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
175
        g_object_unref(G_OBJECT(store));
176
        gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
177
        gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
178
        gtk_tree_view_set_search_column(GTK_TREE_VIEW(treeview), COL_NAME);
179
        gtk_tree_view_set_reorderable(GTK_TREE_VIEW(treeview), FALSE);
180
181
        selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
182
        gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
183
184
        gtk_container_add(GTK_CONTAINER(scrolledwin), treeview);
185
186
        renderer = gtk_cell_renderer_text_new();
187
        g_object_set(renderer, "ypad", 0, NULL);
188
        column = gtk_tree_view_column_new_with_attributes
189
                (_("MIME Type"), renderer, "text", COL_MIMETYPE, NULL);
190
        gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
191
        gtk_tree_view_column_set_resizable(column, TRUE);
192
        gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
193
194
        renderer = gtk_cell_renderer_text_new();
195
        g_object_set(renderer, "xalign", 1.0, "ypad", 0, NULL);
196
        column = gtk_tree_view_column_new_with_attributes
197
                (_("Size"), renderer, "text", COL_SIZE, NULL);
198
        gtk_tree_view_column_set_alignment(column, 1.0);
199
        gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
200
        gtk_tree_view_column_set_resizable(column, TRUE);
201
        gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
202
203
        renderer = gtk_cell_renderer_text_new();
204
        g_object_set(renderer, "ypad", 0, NULL);
205
        column = gtk_tree_view_column_new_with_attributes
206
                (_("Name"), renderer, "text", COL_NAME, NULL);
207
        gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
208
        gtk_tree_view_column_set_resizable(column, TRUE);
209
        gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
210
211
        gtk_tree_view_enable_model_drag_source
212
                (GTK_TREE_VIEW(treeview), GDK_BUTTON1_MASK,
213
                 mimeview_mime_types, 1, GDK_ACTION_COPY);
214
215
        g_signal_connect(G_OBJECT(selection), "changed",
216
                         G_CALLBACK(mimeview_selection_changed), mimeview);
217
        g_signal_connect(G_OBJECT(treeview), "button_press_event",
218
                         G_CALLBACK(mimeview_button_pressed), mimeview);
219
        g_signal_connect(G_OBJECT(treeview), "key_press_event",
220
                         G_CALLBACK(mimeview_key_pressed), mimeview);
221
222
        g_signal_connect_after(G_OBJECT (treeview),"drag-begin",
223
                               G_CALLBACK (mimeview_drag_begin), mimeview);
224
        g_signal_connect(G_OBJECT (treeview),"drag-end",
225
                         G_CALLBACK (mimeview_drag_end), mimeview);
226
        g_signal_connect(G_OBJECT(treeview), "drag-data-get",
227
                         G_CALLBACK(mimeview_drag_data_get), mimeview);
228
    
229
        mime_vbox = gtk_vbox_new(FALSE, 0);
230
        gtk_container_set_reallocate_redraws(GTK_CONTAINER(mime_vbox), TRUE);
231
232
        paned = gtk_vpaned_new();
233
        gtk_paned_add1(GTK_PANED(paned), scrolledwin);
234
        gtk_paned_add2(GTK_PANED(paned), mime_vbox);
235
236
        n_entries = sizeof(mimeview_popup_entries) /
237
                sizeof(mimeview_popup_entries[0]);
238
        popupmenu = menu_create_items(mimeview_popup_entries, n_entries,
239
                                      "<MimeView>", &popupfactory, mimeview);
240
241
        mimeview->paned        = paned;
242
        mimeview->scrolledwin  = scrolledwin;
243
        mimeview->treeview     = treeview;
244
        mimeview->store        = store;
245
        mimeview->selection    = selection;
246
        mimeview->mime_vbox    = mime_vbox;
247
        mimeview->popupmenu    = popupmenu;
248
        mimeview->popupfactory = popupfactory;
249
        mimeview->type         = -1;
250
251
        return mimeview;
252
}
253
254
void mimeview_init(MimeView *mimeview)
255
{
256
        textview_init(mimeview->textview);
257
        imageview_init(mimeview->imageview);
258
}
259
260
/* 
261
 * Check whether the message is OpenPGP signed
262
 */
263
#if USE_GPGME
264
static gboolean mimeview_is_signed(MimeView *mimeview)
265
{
266
        MimeInfo *partinfo;
267
268
        debug_print("mimeview_is signed of %p\n", mimeview);
269
270
        if (!mimeview) return FALSE;
271
        if (!mimeview->opened) return FALSE;
272
273
        debug_print("mimeview_is_signed: open\n" );
274
275
        if (!mimeview->file) return FALSE;
276
277
        debug_print("mimeview_is_signed: file\n" );
278
279
        partinfo = mimeview_get_selected_part(mimeview);
280
        g_return_val_if_fail(partinfo != NULL, FALSE);
281
282
        /* walk the tree and see whether there is a signature somewhere */
283
        do {
284
                if (rfc2015_has_signature(partinfo))
285
                        return TRUE;
286
        } while ((partinfo = partinfo->parent) != NULL);
287
288
        debug_print("mimeview_is_signed: FALSE\n" );
289
290
        return FALSE;
291
}
292
293
static void set_unchecked_signature(MimeInfo *mimeinfo)
294
{
295
        MimeInfo **signedinfo;
296
297
        signedinfo = rfc2015_find_signature(mimeinfo);
298
        if (signedinfo == NULL) return;
299
300
        g_free(signedinfo[1]->sigstatus);
301
        signedinfo[1]->sigstatus =
302
                g_strdup(_("Select \"Check signature\" to check"));
303
304
        g_free(signedinfo[1]->sigstatus_full);
305
        signedinfo[1]->sigstatus_full = NULL;
306
307
        g_free(signedinfo);
308
}
309
#endif /* USE_GPGME */
310
311
void mimeview_show_message(MimeView *mimeview, MimeInfo *mimeinfo,
312
                           const gchar *file)
313
{
314
        GtkTreeModel *model = GTK_TREE_MODEL(mimeview->store);
315
        GtkTreeIter iter;
316
        gboolean valid;
317
318
        mimeview_clear(mimeview);
319
        textview_clear(mimeview->messageview->textview);
320
321
        g_return_if_fail(file != NULL);
322
        g_return_if_fail(mimeinfo != NULL);
323
324
        mimeview->mimeinfo = mimeinfo;
325
326
        mimeview->file = g_strdup(file);
327
328
#if USE_GPGME
329
        if (prefs_common.auto_check_signatures) {
330
                FILE *fp;
331
332
                if ((fp = g_fopen(file, "rb")) == NULL) {
333
                        FILE_OP_ERROR(file, "fopen");
334
                        return;
335
                }
336
                rfc2015_check_signature(mimeinfo, fp);
337
                fclose(fp);
338
        } else
339
                set_unchecked_signature(mimeinfo);
340
#endif
341
342
        g_signal_handlers_block_by_func
343
                (G_OBJECT(mimeview->selection),
344
                 G_CALLBACK(mimeview_selection_changed), mimeview);
345
346
        mimeview_set_multipart_tree(mimeview, mimeinfo, NULL);
347
        gtk_tree_view_expand_all(GTK_TREE_VIEW(mimeview->treeview));
348
349
        g_signal_handlers_unblock_by_func
350
                (G_OBJECT(mimeview->selection),
351
                 G_CALLBACK(mimeview_selection_changed), mimeview);
352
353
        /* search first text part */
354
        for (valid = gtk_tree_model_get_iter_first(model, &iter); valid;
355
             valid = gtkut_tree_model_next(model, &iter)) {
356
                MimeInfo *partinfo;
357
358
                gtk_tree_model_get(model, &iter, COL_MIME_INFO, &partinfo, -1);
359
                if (partinfo &&
360
                    (partinfo->mime_type == MIME_TEXT ||
361
                     partinfo->mime_type == MIME_TEXT_HTML))
362
                        break;
363
        }
364
        textview_show_message(mimeview->messageview->textview, mimeinfo, file);
365
366
        if (!valid)
367
                valid = gtk_tree_model_get_iter_first(model, &iter);
368
369
        if (valid) {
370
                GtkTreePath *path;
371
372
                path = gtk_tree_model_get_path(model, &iter);
373
                gtk_tree_view_set_cursor(GTK_TREE_VIEW(mimeview->treeview),
374
                                         path, NULL, FALSE);
375
                gtk_tree_path_free(path);
376
                if (mimeview_get_selected_part(mimeview))
377
                        gtk_widget_grab_focus(mimeview->treeview);
378
        }
379
}
380
381
void mimeview_clear(MimeView *mimeview)
382
{
383
        procmime_mimeinfo_free_all(mimeview->mimeinfo);
384
        mimeview->mimeinfo = NULL;
385
386
        gtk_tree_store_clear(mimeview->store);
387
        textview_clear(mimeview->textview);
388
        imageview_clear(mimeview->imageview);
389
390
        gtk_tree_path_free(mimeview->opened);
391
        mimeview->opened = NULL;
392
393
        g_free(mimeview->file);
394
        mimeview->file = NULL;
395
396
        g_free(mimeview->drag_file);
397
        mimeview->drag_file = NULL;
398
}
399
400
void mimeview_destroy(MimeView *mimeview)
401
{
402
        textview_destroy(mimeview->textview);
403
        imageview_destroy(mimeview->imageview);
404
        procmime_mimeinfo_free_all(mimeview->mimeinfo);
405
        g_free(mimeview->file);
406
        g_free(mimeview->drag_file);
407
        g_free(mimeview);
408
}
409
410
MimeInfo *mimeview_get_selected_part(MimeView *mimeview)
411
{
412
        GtkTreeModel *model = GTK_TREE_MODEL(mimeview->store);
413
        GtkTreeIter iter;
414
        MimeInfo *partinfo = NULL;
415
416
        if (!mimeview->opened)
417
                return NULL;
418
        if (gtk_notebook_get_current_page
419
                (GTK_NOTEBOOK(mimeview->messageview->notebook)) == 0)
420
                return NULL;
421
422
        if (gtk_tree_model_get_iter(model, &iter, mimeview->opened))
423
                gtk_tree_model_get(model, &iter, COL_MIME_INFO, &partinfo, -1);
424
425
        return partinfo;
426
}
427
428
gboolean mimeview_step(MimeView *mimeview, GtkScrollType type)
429
{
430
        GtkTreeView *treeview = GTK_TREE_VIEW(mimeview->treeview);
431
        GtkTreeModel *model = GTK_TREE_MODEL(mimeview->store);
432
        GtkTreeIter iter;
433
        gboolean moved;
434
435
        if (!mimeview->opened)
436
                return FALSE;
437
        if (!gtk_tree_model_get_iter(model, &iter, mimeview->opened))
438
                return FALSE;
439
440
        if (type == GTK_SCROLL_STEP_FORWARD) {
441
                if (gtkut_tree_model_next(model, &iter))
442
                        gtkut_tree_view_expand_parent_all(treeview, &iter);
443
                else
444
                        return FALSE;
445
        } else {
446
                if (!gtkut_tree_model_prev(model, &iter))
447
                        return FALSE;
448
        }
449
450
        g_signal_emit_by_name(G_OBJECT(treeview), "move-cursor",
451
                              GTK_MOVEMENT_DISPLAY_LINES,
452
                              type == GTK_SCROLL_STEP_FORWARD ? 1 : -1, &moved);
453
454
        return TRUE;
455
}
456
457
static void mimeview_set_multipart_tree(MimeView *mimeview,
458
                                        MimeInfo *mimeinfo,
459
                                        GtkTreeIter *parent)
460
{
461
        GtkTreeIter iter;
462
463
        g_return_if_fail(mimeinfo != NULL);
464
465
        if (mimeinfo->children)
466
                mimeinfo = mimeinfo->children;
467
468
        while (mimeinfo != NULL) {
469
                mimeview_append_part(mimeview, mimeinfo, &iter, parent);
470
471
                if (mimeinfo->children)
472
                        mimeview_set_multipart_tree(mimeview, mimeinfo, &iter);
473
                else if (mimeinfo->sub &&
474
                         mimeinfo->sub->mime_type != MIME_TEXT &&
475
                         mimeinfo->sub->mime_type != MIME_TEXT_HTML)
476
                        mimeview_set_multipart_tree(mimeview, mimeinfo->sub,
477
                                                    &iter);
478
                mimeinfo = mimeinfo->next;
479
        }
480
}
481
482
static gchar *get_part_name(MimeInfo *partinfo)
483
{
484
#if USE_GPGME
485
        if (partinfo->sigstatus)
486
                return partinfo->sigstatus;
487
        else
488
#endif
489
        if (partinfo->name)
490
                return partinfo->name;
491
        else if (partinfo->filename)
492
                return partinfo->filename;
493
        else
494
                return "";
495
}
496
497
static gboolean mimeview_append_part(MimeView *mimeview, MimeInfo *partinfo,
498
                                     GtkTreeIter *iter, GtkTreeIter *parent)
499
{
500
        gchar *mime_type;
501
        gchar *size;
502
        gchar *name;
503
504
        mime_type = partinfo->content_type ? partinfo->content_type : "";
505
        size = to_human_readable(partinfo->content_size);
506
        name = get_part_name(partinfo);
507
508
        gtk_tree_store_append(mimeview->store, iter, parent);
509
        gtk_tree_store_set(mimeview->store, iter,
510
                           COL_MIMETYPE, mime_type,
511
                           COL_SIZE, size,
512
                           COL_NAME, name,
513
                           COL_MIME_INFO, partinfo,
514
                           -1);
515
516
        return TRUE;
517
}
518
519
static void mimeview_show_message_part(MimeView *mimeview, MimeInfo *partinfo)
520
{
521
        FILE *fp;
522
        const gchar *fname;
523
524
        if (!partinfo) return;
525
526
        fname = mimeview->file;
527
        if (!fname) return;
528
529
        if ((fp = g_fopen(fname, "rb")) == NULL) {
530
                FILE_OP_ERROR(fname, "fopen");
531
                return;
532
        }
533
534
        if (fseek(fp, partinfo->fpos, SEEK_SET) < 0) {
535
                FILE_OP_ERROR(mimeview->file, "fseek");
536
                fclose(fp);
537
                return;
538
        }
539
540
        mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
541
        textview_show_part(mimeview->textview, partinfo, fp);
542
543
        fclose(fp);
544
}
545
546
static void mimeview_show_image_part(MimeView *mimeview, MimeInfo *partinfo)
547
{
548
        gchar *filename;
549
550
        if (!partinfo) return;
551
552
        filename = procmime_get_tmp_file_name(partinfo);
553
554
        if (procmime_get_part(filename, mimeview->file, partinfo) < 0)
555
                alertpanel_error
556
                        (_("Can't get the part of multipart message."));
557
        else {
558
                mimeview_change_view_type(mimeview, MIMEVIEW_IMAGE);
559
                imageview_show_image(mimeview->imageview, partinfo, filename,
560
                                     prefs_common.resize_image);
561
                g_unlink(filename);
562
        }
563
564
        g_free(filename);
565
}
566
567
static void save_as_button_clicked(GtkWidget *widget, gpointer data)
568
{
569
        MimeView *mimeview = (MimeView *)data;
570
571
        mimeview_save_as(mimeview);
572
}
573
574
static void display_as_text_button_clicked(GtkWidget *widget, gpointer data)
575
{
576
        MimeView *mimeview = (MimeView *)data;
577
578
        mimeview_display_as_text(mimeview);
579
}
580
581
static void open_button_clicked(GtkWidget *widget, gpointer data)
582
{
583
        MimeView *mimeview = (MimeView *)data;
584
585
        mimeview_launch(mimeview);
586
}
587
588
static void open_with_button_clicked(GtkWidget *widget, gpointer data)
589
{
590
        MimeView *mimeview = (MimeView *)data;
591
592
        mimeview_open_with(mimeview);
593
}
594
595
static void mimeview_show_mime_part(MimeView *mimeview, MimeInfo *partinfo)
596
{
597
        TextView *textview = mimeview->textview;
598
        GtkTextBuffer *buffer;
599
        GtkTextIter iter;
600
        GtkTextChildAnchor *anchor;
601
        GtkWidget *vbbox;
602
        GtkWidget *button;
603
        gchar buf[BUFFSIZE];
604
605
        if (!partinfo) return;
606
607
        textview_set_font(textview, NULL);
608
        textview_clear(textview);
609
610
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
611
        gtk_text_buffer_get_start_iter(buffer, &iter);
612
613
        gtk_text_buffer_insert(buffer, &iter,
614
                               _("Select an action for the attached file:\n"),
615
                               -1);
616
        if (partinfo->filename || partinfo->name)
617
                g_snprintf(buf, sizeof(buf), "[%s  %s (%s)]\n\n",
618
                           partinfo->filename ? partinfo->filename :
619
                           partinfo->name,
620
                           partinfo->content_type,
621
                           to_human_readable(partinfo->content_size));
622
        else
623
                g_snprintf(buf, sizeof(buf), "[%s (%s)]\n\n",
624
                           partinfo->content_type,
625
                           to_human_readable(partinfo->content_size));
626
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, buf, -1,
627
                                                 "mimepart", NULL);
628
629
        vbbox = gtk_vbutton_box_new();
630
        gtk_box_set_spacing(GTK_BOX(vbbox), 5);
631
632
        button = gtk_button_new_from_stock(GTK_STOCK_OPEN);
633
        gtk_container_add(GTK_CONTAINER(vbbox), button);
634
        g_signal_connect(button, "clicked", G_CALLBACK(open_button_clicked),
635
                         mimeview);
636
        button = gtk_button_new_with_mnemonic(_("Open _with..."));
637
        gtk_container_add(GTK_CONTAINER(vbbox), button);
638
        g_signal_connect(button, "clicked",
639
                         G_CALLBACK(open_with_button_clicked), mimeview);
640
        button = gtk_button_new_with_mnemonic(_("_Display as text"));
641
        gtk_container_add(GTK_CONTAINER(vbbox), button);
642
        g_signal_connect(button, "clicked",
643
                         G_CALLBACK(display_as_text_button_clicked), mimeview);
644
        button = gtk_button_new_with_mnemonic(_("_Save as..."));
645
        gtk_container_add(GTK_CONTAINER(vbbox), button);
646
        g_signal_connect(button, "clicked", G_CALLBACK(save_as_button_clicked),
647
                         mimeview);
648
649
        gtk_widget_show_all(vbbox);
650
651
        anchor = gtk_text_buffer_create_child_anchor(buffer, &iter);
652
        gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(textview->text),
653
                                          vbbox, anchor);
654
}
655
656
#if USE_GPGME
657
static void check_signature_button_clicked(GtkWidget *widget, gpointer data)
658
{
659
        MimeView *mimeview = (MimeView *)data;
660
661
        mimeview_check_signature(mimeview);
662
}
663
664
static void mimeview_show_signature_part(MimeView *mimeview,
665
                                         MimeInfo *partinfo)
666
{
667
        TextView *textview = mimeview->textview;
668
        GtkTextBuffer *buffer;
669
        GtkTextIter iter;
670
        GtkTextChildAnchor *anchor;
671
        GtkWidget *vbbox;
672
        GtkWidget *button;
673
674
        if (!partinfo) return;
675
676
        textview_set_font(textview, NULL);
677
        textview_clear(textview);
678
679
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
680
        gtk_text_buffer_get_start_iter(buffer, &iter);
681
682
        if (partinfo->sigstatus_full) {
683
                gtk_text_buffer_insert
684
                        (buffer, &iter, partinfo->sigstatus_full, -1);
685
                return;
686
        }
687
688
        gtk_text_buffer_insert
689
                (buffer, &iter,
690
                 _("This signature has not been checked yet.\n\n"), -1);
691
692
        vbbox = gtk_vbutton_box_new();
693
        gtk_box_set_spacing(GTK_BOX(vbbox), 5);
694
695
        button = gtk_button_new_with_mnemonic(_("_Check signature"));
696
        gtk_container_add(GTK_CONTAINER(vbbox), button);
697
        g_signal_connect(button, "clicked",
698
                         G_CALLBACK(check_signature_button_clicked), mimeview);
699
700
        gtk_widget_show_all(vbbox);
701
702
        anchor = gtk_text_buffer_create_child_anchor(buffer, &iter);
703
        gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(textview->text),
704
                                          vbbox, anchor);
705
}
706
#endif /* USE_GPGME */
707
708
static void mimeview_change_view_type(MimeView *mimeview, MimeViewType type)
709
{
710
        TextView  *textview  = mimeview->textview;
711
        ImageView *imageview = mimeview->imageview;
712
        GList *children;
713
714
        if (mimeview->type == type) return;
715
716
        children = gtk_container_get_children
717
                (GTK_CONTAINER(mimeview->mime_vbox));
718
        if (children) {
719
                gtkut_container_remove(GTK_CONTAINER(mimeview->mime_vbox),
720
                                       GTK_WIDGET(children->data));
721
                g_list_free(children);
722
        }
723
724
        switch (mimeview->type) {
725
        case MIMEVIEW_IMAGE:
726
                imageview_clear(mimeview->imageview);
727
                break;
728
        case MIMEVIEW_TEXT:
729
                textview_clear(mimeview->textview);
730
                break;
731
        default:
732
                break;
733
        }
734
735
        switch (type) {
736
        case MIMEVIEW_IMAGE:
737
                gtk_container_add(GTK_CONTAINER(mimeview->mime_vbox),
738
                                  GTK_WIDGET_PTR(imageview));
739
                break;
740
        case MIMEVIEW_TEXT:
741
                gtk_container_add(GTK_CONTAINER(mimeview->mime_vbox),
742
                                  GTK_WIDGET_PTR(textview));
743
                break;
744
        default:
745
                return;
746
        }
747
748
        mimeview->type = type;
749
}
750
751
static void mimeview_selection_changed(GtkTreeSelection *selection,
752
                                       MimeView *mimeview)
753
{
754
        GtkTreeModel *model = GTK_TREE_MODEL(mimeview->store);
755
        GtkTreeIter iter;
756
        GtkTreePath *path;
757
        MimeInfo *partinfo;
758
759
        if (!gtk_tree_selection_get_selected(selection, NULL, &iter)) {
760
                if (mimeview->opened) {
761
                        gtk_tree_path_free(mimeview->opened);
762
                        mimeview->opened = NULL;
763
                }
764
                return;
765
        }
766
767
        path = gtk_tree_model_get_path(model, &iter);
768
769
        if (mimeview->opened &&
770
            gtk_tree_path_compare(mimeview->opened, path) == 0) {
771
                gtk_tree_path_free(path);
772
                return;
773
        }
774
775
        gtk_tree_path_free(mimeview->opened);
776
        mimeview->opened = path;
777
        path = NULL;
778
779
        gtk_tree_model_get(model, &iter, COL_MIME_INFO, &partinfo, -1);
780
        if (!partinfo)
781
                return;
782
783
        switch (partinfo->mime_type) {
784
        case MIME_TEXT:
785
        case MIME_TEXT_HTML:
786
        case MIME_MESSAGE_RFC822:
787
        case MIME_MULTIPART:
788
                mimeview_show_message_part(mimeview, partinfo);
789
                break;
790
        case MIME_IMAGE:
791
                mimeview_show_image_part(mimeview, partinfo);
792
                break;
793
        default:
794
                mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
795
#if USE_GPGME
796
                if (rfc2015_is_signature_part(partinfo))
797
                        mimeview_show_signature_part(mimeview, partinfo);
798
                else
799
#endif
800
                        mimeview_show_mime_part(mimeview, partinfo);
801
                break;
802
        }
803
}
804
805
static gint mimeview_button_pressed(GtkWidget *widget, GdkEventButton *event,
806
                                    MimeView *mimeview)
807
{
808
        GtkTreeView *treeview = GTK_TREE_VIEW(widget);
809
        MimeInfo *partinfo;
810
811
        if (!event) return FALSE;
812
813
        if (event->button == 2 || event->button == 3) {
814
                GtkTreePath *path;
815
816
                if (!gtk_tree_view_get_path_at_pos(treeview, event->x, event->y,
817
                                                   &path, NULL, NULL, NULL))
818
                        return FALSE;
819
                gtk_tree_view_set_cursor(treeview, path, NULL, FALSE);
820
                gtk_tree_path_free(path);
821
        }
822
823
        if (event->button == 2 ||
824
            (event->button == 1 && event->type == GDK_2BUTTON_PRESS)) {
825
                /* call external program for image, audio or html */
826
                mimeview_launch(mimeview);
827
        } else if (event->button == 3) {
828
                partinfo = mimeview_get_selected_part(mimeview);
829
                if (partinfo && (partinfo->mime_type == MIME_TEXT ||
830
                                 partinfo->mime_type == MIME_TEXT_HTML ||
831
                                 partinfo->mime_type == MIME_MESSAGE_RFC822 ||
832
                                 partinfo->mime_type == MIME_IMAGE ||
833
                                 partinfo->mime_type == MIME_MULTIPART))
834
                        menu_set_sensitive(mimeview->popupfactory,
835
                                           "/Display as text", FALSE);
836
                else
837
                        menu_set_sensitive(mimeview->popupfactory,
838
                                           "/Display as text", TRUE);
839
                if (partinfo &&
840
                    partinfo->mime_type == MIME_APPLICATION_OCTET_STREAM)
841
                        menu_set_sensitive(mimeview->popupfactory,
842
                                           "/Open", FALSE);
843
                else
844
                        menu_set_sensitive(mimeview->popupfactory,
845
                                           "/Open", TRUE);
846
#if USE_GPGME
847
                menu_set_sensitive(mimeview->popupfactory,
848
                                   "/Check signature",
849
                                   mimeview_is_signed(mimeview));
850
#endif
851
852
                gtk_menu_popup(GTK_MENU(mimeview->popupmenu),
853
                               NULL, NULL, NULL, NULL,
854
                               event->button, event->time);
855
                return TRUE;
856
        }
857
858
        return FALSE;
859
}
860
861
void mimeview_pass_key_press_event(MimeView *mimeview, GdkEventKey *event)
862
{
863
        mimeview_key_pressed(mimeview->treeview, event, mimeview);
864
}
865
866
#define BREAK_ON_MODIFIER_KEY() \
867
        if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) != 0) break
868
869
#define KEY_PRESS_EVENT_STOP() \
870
        g_signal_stop_emission_by_name(G_OBJECT(treeview), "key_press_event");
871
872
static gint mimeview_key_pressed(GtkWidget *widget, GdkEventKey *event,
873
                                 MimeView *mimeview)
874
{
875
        SummaryView *summaryview = NULL;
876
        GtkTreeView *treeview = GTK_TREE_VIEW(widget);
877
        GtkTreeModel *model = GTK_TREE_MODEL(mimeview->store);
878
        GtkTreeIter iter;
879
        gboolean mod_pressed;
880
881
        if (!event) return FALSE;
882
        if (!mimeview->opened) return FALSE;
883
        if (!gtk_tree_model_get_iter(model, &iter, mimeview->opened))
884
                return FALSE;
885
886
        if (mimeview->messageview->mainwin)
887
                summaryview = mimeview->messageview->mainwin->summaryview;
888
        mod_pressed =
889
                ((event->state & (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
890
891
        switch (event->keyval) {
892
        case GDK_space:
893
        case GDK_KP_Space:
894
                if (textview_scroll_page(mimeview->textview, mod_pressed))
895
                        return TRUE;
896
897
                if (gtkut_tree_model_next(model, &iter)) {
898
                        GtkTreePath *path;
899
900
                        path = gtk_tree_model_get_path(model, &iter);
901
                        gtk_tree_view_set_cursor(treeview, path, NULL, FALSE);
902
                        gtk_tree_path_free(path);
903
                        return TRUE;
904
                }
905
                if (summaryview)
906
                        summary_pass_key_press_event(summaryview, event);
907
                break;
908
        case GDK_BackSpace:
909
                textview_scroll_page(mimeview->textview, TRUE);
910
                return TRUE;
911
        case GDK_Return:
912
        case GDK_KP_Enter:
913
                textview_scroll_one_line(mimeview->textview, mod_pressed);
914
                return TRUE;
915
        case GDK_t:
916
                BREAK_ON_MODIFIER_KEY();
917
                KEY_PRESS_EVENT_STOP();
918
                mimeview_display_as_text(mimeview);
919
                return TRUE;
920
        case GDK_Escape:
921
                if (summaryview)
922
                        gtk_widget_grab_focus(summaryview->treeview);
923
                break;
924
        case GDK_Left:
925
        case GDK_Delete:
926
        case GDK_KP_Left:
927
        case GDK_KP_Delete:
928
                if (summaryview)
929
                        summary_pass_key_press_event(summaryview, event);
930
                break;
931
        default:
932
                break;
933
        }
934
935
        return FALSE;
936
}
937
938
static void mimeview_drag_begin(GtkWidget *widget, GdkDragContext *drag_context,
939
                                MimeView *mimeview)
940
{
941
        gchar *filename;
942
        gchar *bname = NULL;
943
        MimeInfo *partinfo;
944
945
        if (!mimeview->opened) return;
946
        if (!mimeview->file) return;
947
948
        partinfo = mimeview_get_selected_part(mimeview);
949
        if (!partinfo) return;
950
951
        filename = partinfo->filename ? partinfo->filename : partinfo->name;
952
        if (filename) {
953
                const gchar *bname_;
954
955
                bname_ = g_basename(filename);
956
                bname = conv_filename_from_utf8(bname_);
957
                subst_for_filename(bname);
958
        }
959
        if (!bname || *bname == '\0')
960
                filename = procmime_get_tmp_file_name(partinfo);
961
        else
962
                filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
963
                                       bname, NULL);
964
965
        if (procmime_get_part(filename, mimeview->file, partinfo) < 0) {
966
                g_warning(_("Can't save the part of multipart message."));
967
        } else
968
                mimeview->drag_file = encode_uri(filename);
969
970
        g_free(filename);
971
972
        gtk_drag_set_icon_default(drag_context);
973
}
974
975
static void mimeview_drag_end(GtkWidget *widget, GdkDragContext *drag_context,
976
                              MimeView *mimeview)
977
{
978
        if (mimeview->drag_file) {
979
                g_free(mimeview->drag_file);
980
                mimeview->drag_file = NULL;
981
        }
982
}
983
984
static void mimeview_drag_data_get(GtkWidget            *widget,
985
                                   GdkDragContext   *drag_context,
986
                                   GtkSelectionData *selection_data,
987
                                   guint             info,
988
                                   guint             time,
989
                                   MimeView            *mimeview)
990
{
991
        if (!mimeview->drag_file) return;
992
993
        gtk_selection_data_set(selection_data, selection_data->target, 8,
994
                               (guchar *)mimeview->drag_file,
995
                               strlen(mimeview->drag_file));
996
}
997
998
static void mimeview_display_as_text(MimeView *mimeview)
999
{
1000
        MimeInfo *partinfo;
1001
1002
        if (!mimeview->opened) return;
1003
1004
        partinfo = mimeview_get_selected_part(mimeview);
1005
        g_return_if_fail(partinfo != NULL);
1006
        mimeview_show_message_part(mimeview, partinfo);
1007
}
1008
1009
void mimeview_save_as(MimeView *mimeview)
1010
{
1011
        gchar *filename;
1012
        gchar *defname = NULL;
1013
        MimeInfo *partinfo;
1014
1015
        if (!mimeview->opened) return;
1016
        if (!mimeview->file) return;
1017
1018
        partinfo = mimeview_get_selected_part(mimeview);
1019
        g_return_if_fail(partinfo != NULL);
1020
1021
        if (partinfo->filename)
1022
                defname = partinfo->filename;
1023
        else if (partinfo->name) {
1024
                Xstrdup_a(defname, partinfo->name, return);
1025
                subst_for_filename(defname);
1026
        }
1027
1028
        filename = filesel_save_as(defname);
1029
        if (!filename) return;
1030
1031
        if (procmime_get_part(filename, mimeview->file, partinfo) < 0)
1032
                alertpanel_error
1033
                        (_("Can't save the part of multipart message."));
1034
1035
        g_free(filename);
1036
}
1037
1038
void mimeview_save_all(MimeView *mimeview)
1039
{
1040
        gchar *dir;
1041
1042
        dir = filesel_select_dir(NULL);
1043
        if (!dir) return;
1044
1045
        if (procmime_get_all_parts(dir, mimeview->file, mimeview->mimeinfo) < 0)
1046
                alertpanel_error(_("Can't save the attachments."));
1047
1048
        g_free(dir);
1049
}
1050
1051
static void mimeview_launch(MimeView *mimeview)
1052
{
1053
        MimeInfo *partinfo;
1054
        gchar *filename;
1055
1056
        if (!mimeview->opened) return;
1057
        if (!mimeview->file) return;
1058
1059
        partinfo = mimeview_get_selected_part(mimeview);
1060
        g_return_if_fail(partinfo != NULL);
1061
1062
        filename = procmime_get_tmp_file_name(partinfo);
1063
1064
        if (procmime_get_part(filename, mimeview->file, partinfo) < 0)
1065
                alertpanel_error
1066
                        (_("Can't save the part of multipart message."));
1067
        else
1068
                mimeview_view_file(filename, partinfo, NULL);
1069
1070
        g_free(filename);
1071
}
1072
1073
static void mimeview_open_with(MimeView *mimeview)
1074
{
1075
        MimeInfo *partinfo;
1076
        gchar *filename;
1077
        gchar *cmd;
1078
1079
        if (!mimeview->opened) return;
1080
        if (!mimeview->file) return;
1081
1082
        partinfo = mimeview_get_selected_part(mimeview);
1083
        g_return_if_fail(partinfo != NULL);
1084
1085
        filename = procmime_get_tmp_file_name(partinfo);
1086
1087
        if (procmime_get_part(filename, mimeview->file, partinfo) < 0) {
1088
                alertpanel_error
1089
                        (_("Can't save the part of multipart message."));
1090
                g_free(filename);
1091
                return;
1092
        }
1093
1094
        if (!prefs_common.mime_open_cmd_history)
1095
                prefs_common.mime_open_cmd_history =
1096
                        add_history(NULL, prefs_common.mime_open_cmd);
1097
1098
        cmd = input_dialog_combo
1099
                (_("Open with"),
1100
                 _("Enter the command line to open file:\n"
1101
                   "(`%s' will be replaced with file name)"),
1102
                 prefs_common.mime_open_cmd,
1103
                 prefs_common.mime_open_cmd_history,
1104
                 TRUE);
1105
        if (cmd) {
1106
                mimeview_view_file(filename, partinfo, cmd);
1107
                g_free(prefs_common.mime_open_cmd);
1108
                prefs_common.mime_open_cmd = cmd;
1109
                prefs_common.mime_open_cmd_history =
1110
                        add_history(prefs_common.mime_open_cmd_history, cmd);
1111
        }
1112
1113
        g_free(filename);
1114
}
1115
1116
static void mimeview_view_file(const gchar *filename, MimeInfo *partinfo,
1117
                               const gchar *cmdline)
1118
{
1119
        const gchar *cmd = NULL;
1120
        gchar buf[BUFFSIZE];
1121
1122
        if (!cmdline) {
1123
#ifdef G_OS_WIN32
1124
                DWORD dwtype;
1125
1126
                if (g_file_test(filename, G_FILE_TEST_IS_EXECUTABLE) ||
1127
                    str_has_suffix_case(filename, ".scr") ||
1128
                    str_has_suffix_case(filename, ".pif") ||
1129
                    GetBinaryType(filename, &dwtype)) {
1130
                        alertpanel_full
1131
                                (_("Opening executable file"),
1132
                                 _("This is an executable file. Opening executable file is restricted for security.\n"
1133
                                   "If you want to launch it, save it to somewhere and make sure it is not an virus or something like a malicious program."),
1134
                                 ALERT_WARNING, G_ALERTDEFAULT, FALSE,
1135
                                 GTK_STOCK_OK, NULL, NULL);
1136
                        return;
1137
                }
1138
                execute_open_file(filename, partinfo->content_type);
1139
                return;
1140
#else
1141
                if (MIME_IMAGE == partinfo->mime_type)
1142
                        cmd = prefs_common.mime_image_viewer;
1143
                else if (MIME_AUDIO == partinfo->mime_type)
1144
                        cmd = prefs_common.mime_audio_player;
1145
                else if (MIME_TEXT_HTML == partinfo->mime_type)
1146
                        cmd = prefs_common.uri_cmd;
1147
                if (!cmd) {
1148
                        if (prefs_common.mime_cmd) {
1149
                                if (str_find_format_times
1150
                                        (prefs_common.mime_cmd, 's') == 2) {
1151
                                        g_snprintf(buf, sizeof(buf),
1152
                                                   prefs_common.mime_cmd,
1153
                                                   partinfo->content_type,
1154
                                                   "%s");
1155
                                        cmd = buf;
1156
                                } else
1157
                                        cmd = prefs_common.mime_cmd;
1158
                        } else {
1159
                                procmime_execute_open_file
1160
                                        (filename, partinfo->content_type);
1161
                                return;
1162
                        }
1163
                }
1164
#endif
1165
        } else
1166
                cmd = cmdline;
1167
1168
        if (cmd && str_find_format_times(cmd, 's') == 1) {
1169
                gchar *cmdbuf;
1170
                cmdbuf = g_strdup_printf(cmd, filename);
1171
                execute_command_line(cmdbuf, TRUE);
1172
                g_free(cmdbuf);
1173
        } else if (cmd)
1174
                g_warning("MIME viewer command line is invalid: '%s'", cmd);
1175
}
1176
1177
#if USE_GPGME
1178
static gboolean update_node_name_func(GtkTreeModel *model, GtkTreePath *path,
1179
                                      GtkTreeIter *iter, gpointer data)
1180
{
1181
        MimeInfo *partinfo;
1182
        gchar *part_name;
1183
1184
        gtk_tree_model_get(model, iter, COL_MIME_INFO, &partinfo, -1);
1185
        g_return_val_if_fail(partinfo != NULL, FALSE);
1186
1187
        part_name = get_part_name(partinfo);
1188
        gtk_tree_store_set(GTK_TREE_STORE(model), iter, COL_NAME, part_name,
1189
                           -1);
1190
1191
        return FALSE;
1192
}
1193
1194
static void mimeview_update_names(MimeView *mimeview)
1195
{
1196
        gtk_tree_model_foreach(GTK_TREE_MODEL(mimeview->store),
1197
                               update_node_name_func, NULL);
1198
}
1199
1200
static void mimeview_update_signature_info(MimeView *mimeview)
1201
{
1202
        MimeInfo *partinfo;
1203
1204
        if (!mimeview) return;
1205
        if (!mimeview->opened) return;
1206
1207
        partinfo = mimeview_get_selected_part(mimeview);
1208
        if (!partinfo) return;
1209
1210
        if (rfc2015_is_signature_part(partinfo)) {
1211
                mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
1212
                mimeview_show_signature_part(mimeview, partinfo);
1213
        }
1214
}
1215
1216
static void mimeview_check_signature(MimeView *mimeview)
1217
{
1218
        MimeInfo *mimeinfo;
1219
        FILE *fp;
1220
1221
        g_return_if_fail (mimeview_is_signed(mimeview));
1222
1223
        mimeinfo = mimeview_get_selected_part(mimeview);
1224
        g_return_if_fail(mimeinfo != NULL);
1225
        g_return_if_fail(mimeview->file != NULL);
1226
1227
        while (mimeinfo->parent)
1228
                mimeinfo = mimeinfo->parent;
1229
1230
        if ((fp = g_fopen(mimeview->file, "rb")) == NULL) {
1231
                FILE_OP_ERROR(mimeview->file, "fopen");
1232
                return;
1233
        }
1234
1235
        rfc2015_check_signature(mimeinfo, fp);
1236
        fclose(fp);
1237
1238
        mimeview_update_names(mimeview);
1239
        mimeview_update_signature_info(mimeview);
1240
1241
        textview_show_message(mimeview->messageview->textview, mimeinfo,
1242
                              mimeview->file);
1243
}
1244
#endif /* USE_GPGME */