Statistics
| Revision:

root / src / mimeview.c @ 642

History | View | Annotate | Download (33 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), 0, mimeview_mime_types, 1,
213
                 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
#if USE_GPGME
524
        MimeInfo *pi;
525
#endif
526
527
        if (!partinfo) return;
528
529
#if USE_GPGME
530
        for (pi = partinfo; pi && !pi->plaintextfile ; pi = pi->parent)
531
                ;
532
        fname = pi ? pi->plaintextfile : mimeview->file;
533
#else
534
        fname = mimeview->file;
535
#endif /* USE_GPGME */
536
        if (!fname) return;
537
538
        if ((fp = g_fopen(fname, "rb")) == NULL) {
539
                FILE_OP_ERROR(fname, "fopen");
540
                return;
541
        }
542
543
        if (fseek(fp, partinfo->fpos, SEEK_SET) < 0) {
544
                FILE_OP_ERROR(mimeview->file, "fseek");
545
                fclose(fp);
546
                return;
547
        }
548
549
        mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
550
        textview_show_part(mimeview->textview, partinfo, fp);
551
552
        fclose(fp);
553
}
554
555
static void mimeview_show_image_part(MimeView *mimeview, MimeInfo *partinfo)
556
{
557
        gchar *filename;
558
559
        if (!partinfo) return;
560
561
        filename = procmime_get_tmp_file_name(partinfo);
562
563
        if (procmime_get_part(filename, mimeview->file, partinfo) < 0)
564
                alertpanel_error
565
                        (_("Can't get the part of multipart message."));
566
        else {
567
                mimeview_change_view_type(mimeview, MIMEVIEW_IMAGE);
568
                imageview_show_image(mimeview->imageview, partinfo, filename,
569
                                     prefs_common.resize_image);
570
                g_unlink(filename);
571
        }
572
573
        g_free(filename);
574
}
575
576
static void save_as_button_clicked(GtkWidget *widget, gpointer data)
577
{
578
        MimeView *mimeview = (MimeView *)data;
579
580
        mimeview_save_as(mimeview);
581
}
582
583
static void display_as_text_button_clicked(GtkWidget *widget, gpointer data)
584
{
585
        MimeView *mimeview = (MimeView *)data;
586
587
        mimeview_display_as_text(mimeview);
588
}
589
590
static void open_button_clicked(GtkWidget *widget, gpointer data)
591
{
592
        MimeView *mimeview = (MimeView *)data;
593
594
        mimeview_launch(mimeview);
595
}
596
597
static void open_with_button_clicked(GtkWidget *widget, gpointer data)
598
{
599
        MimeView *mimeview = (MimeView *)data;
600
601
        mimeview_open_with(mimeview);
602
}
603
604
static void mimeview_show_mime_part(MimeView *mimeview, MimeInfo *partinfo)
605
{
606
        TextView *textview = mimeview->textview;
607
        GtkTextBuffer *buffer;
608
        GtkTextIter iter;
609
        GtkTextChildAnchor *anchor;
610
        GtkWidget *vbbox;
611
        GtkWidget *button;
612
        gchar buf[BUFFSIZE];
613
614
        if (!partinfo) return;
615
616
        textview_set_font(textview, NULL);
617
        textview_clear(textview);
618
619
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
620
        gtk_text_buffer_get_start_iter(buffer, &iter);
621
622
        gtk_text_buffer_insert(buffer, &iter,
623
                               _("Select an action for the attached file:\n"),
624
                               -1);
625
        if (partinfo->filename || partinfo->name)
626
                g_snprintf(buf, sizeof(buf), "[%s  %s (%s)]\n\n",
627
                           partinfo->filename ? partinfo->filename :
628
                           partinfo->name,
629
                           partinfo->content_type,
630
                           to_human_readable(partinfo->content_size));
631
        else
632
                g_snprintf(buf, sizeof(buf), "[%s (%s)]\n\n",
633
                           partinfo->content_type,
634
                           to_human_readable(partinfo->content_size));
635
        gtk_text_buffer_insert(buffer, &iter, buf, -1);
636
637
        vbbox = gtk_vbutton_box_new();
638
        gtk_box_set_spacing(GTK_BOX(vbbox), 5);
639
640
        button = gtk_button_new_from_stock(GTK_STOCK_OPEN);
641
        gtk_container_add(GTK_CONTAINER(vbbox), button);
642
        g_signal_connect(button, "clicked", G_CALLBACK(open_button_clicked),
643
                         mimeview);
644
        button = gtk_button_new_with_mnemonic(_("Open _with..."));
645
        gtk_container_add(GTK_CONTAINER(vbbox), button);
646
        g_signal_connect(button, "clicked",
647
                         G_CALLBACK(open_with_button_clicked), mimeview);
648
        button = gtk_button_new_with_mnemonic(_("_Display as text"));
649
        gtk_container_add(GTK_CONTAINER(vbbox), button);
650
        g_signal_connect(button, "clicked",
651
                         G_CALLBACK(display_as_text_button_clicked), mimeview);
652
        button = gtk_button_new_with_mnemonic(_("_Save as..."));
653
        gtk_container_add(GTK_CONTAINER(vbbox), button);
654
        g_signal_connect(button, "clicked", G_CALLBACK(save_as_button_clicked),
655
                         mimeview);
656
657
        gtk_widget_show_all(vbbox);
658
659
        anchor = gtk_text_buffer_create_child_anchor(buffer, &iter);
660
        gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(textview->text),
661
                                          vbbox, anchor);
662
}
663
664
#if USE_GPGME
665
static void check_signature_button_clicked(GtkWidget *widget, gpointer data)
666
{
667
        MimeView *mimeview = (MimeView *)data;
668
669
        mimeview_check_signature(mimeview);
670
}
671
672
static void mimeview_show_signature_part(MimeView *mimeview,
673
                                         MimeInfo *partinfo)
674
{
675
        TextView *textview = mimeview->textview;
676
        GtkTextBuffer *buffer;
677
        GtkTextIter iter;
678
        GtkTextChildAnchor *anchor;
679
        GtkWidget *vbbox;
680
        GtkWidget *button;
681
682
        if (!partinfo) return;
683
684
        textview_set_font(textview, NULL);
685
        textview_clear(textview);
686
687
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
688
        gtk_text_buffer_get_start_iter(buffer, &iter);
689
690
        if (partinfo->sigstatus_full) {
691
                gtk_text_buffer_insert
692
                        (buffer, &iter, partinfo->sigstatus_full, -1);
693
                return;
694
        }
695
696
        gtk_text_buffer_insert
697
                (buffer, &iter,
698
                 _("This signature has not been checked yet.\n\n"), -1);
699
700
        vbbox = gtk_vbutton_box_new();
701
        gtk_box_set_spacing(GTK_BOX(vbbox), 5);
702
703
        button = gtk_button_new_with_mnemonic(_("_Check signature"));
704
        gtk_container_add(GTK_CONTAINER(vbbox), button);
705
        g_signal_connect(button, "clicked",
706
                         G_CALLBACK(check_signature_button_clicked), mimeview);
707
708
        gtk_widget_show_all(vbbox);
709
710
        anchor = gtk_text_buffer_create_child_anchor(buffer, &iter);
711
        gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(textview->text),
712
                                          vbbox, anchor);
713
}
714
#endif /* USE_GPGME */
715
716
static void mimeview_change_view_type(MimeView *mimeview, MimeViewType type)
717
{
718
        TextView  *textview  = mimeview->textview;
719
        ImageView *imageview = mimeview->imageview;
720
        GList *children;
721
722
        if (mimeview->type == type) return;
723
724
        children = gtk_container_get_children
725
                (GTK_CONTAINER(mimeview->mime_vbox));
726
        if (children) {
727
                gtkut_container_remove(GTK_CONTAINER(mimeview->mime_vbox),
728
                                       GTK_WIDGET(children->data));
729
                g_list_free(children);
730
        }
731
732
        switch (type) {
733
        case MIMEVIEW_IMAGE:
734
                gtk_container_add(GTK_CONTAINER(mimeview->mime_vbox),
735
                                  GTK_WIDGET_PTR(imageview));
736
                break;
737
        case MIMEVIEW_TEXT:
738
                gtk_container_add(GTK_CONTAINER(mimeview->mime_vbox),
739
                                  GTK_WIDGET_PTR(textview));
740
                break;
741
        default:
742
                return;
743
        }
744
745
        mimeview->type = type;
746
}
747
748
static void mimeview_selection_changed(GtkTreeSelection *selection,
749
                                       MimeView *mimeview)
750
{
751
        GtkTreeModel *model = GTK_TREE_MODEL(mimeview->store);
752
        GtkTreeIter iter;
753
        GtkTreePath *path;
754
        MimeInfo *partinfo;
755
756
        if (!gtk_tree_selection_get_selected(selection, NULL, &iter)) {
757
                if (mimeview->opened) {
758
                        gtk_tree_path_free(mimeview->opened);
759
                        mimeview->opened = NULL;
760
                }
761
                return;
762
        }
763
764
        path = gtk_tree_model_get_path(model, &iter);
765
766
        if (mimeview->opened &&
767
            gtk_tree_path_compare(mimeview->opened, path) == 0) {
768
                gtk_tree_path_free(path);
769
                return;
770
        }
771
772
        gtk_tree_path_free(mimeview->opened);
773
        mimeview->opened = path;
774
        path = NULL;
775
776
        gtk_tree_model_get(model, &iter, COL_MIME_INFO, &partinfo, -1);
777
        if (!partinfo)
778
                return;
779
780
        switch (partinfo->mime_type) {
781
        case MIME_TEXT:
782
        case MIME_TEXT_HTML:
783
        case MIME_MESSAGE_RFC822:
784
        case MIME_MULTIPART:
785
                mimeview_show_message_part(mimeview, partinfo);
786
                break;
787
        case MIME_IMAGE:
788
                mimeview_show_image_part(mimeview, partinfo);
789
                break;
790
        default:
791
                mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
792
#if USE_GPGME
793
                if (rfc2015_is_signature_part(partinfo))
794
                        mimeview_show_signature_part(mimeview, partinfo);
795
                else
796
#endif
797
                        mimeview_show_mime_part(mimeview, partinfo);
798
                break;
799
        }
800
}
801
802
static gint mimeview_button_pressed(GtkWidget *widget, GdkEventButton *event,
803
                                    MimeView *mimeview)
804
{
805
        GtkTreeView *treeview = GTK_TREE_VIEW(widget);
806
        MimeInfo *partinfo;
807
808
        if (!event) return FALSE;
809
810
        if (event->button == 2 || event->button == 3) {
811
                GtkTreePath *path;
812
813
                if (!gtk_tree_view_get_path_at_pos(treeview, event->x, event->y,
814
                                                   &path, NULL, NULL, NULL))
815
                        return FALSE;
816
                gtk_tree_view_set_cursor(treeview, path, NULL, FALSE);
817
                gtk_tree_path_free(path);
818
        }
819
820
        if (event->button == 2 ||
821
            (event->button == 1 && event->type == GDK_2BUTTON_PRESS)) {
822
                /* call external program for image, audio or html */
823
                mimeview_launch(mimeview);
824
        } else if (event->button == 3) {
825
                partinfo = mimeview_get_selected_part(mimeview);
826
                if (partinfo && (partinfo->mime_type == MIME_TEXT ||
827
                                 partinfo->mime_type == MIME_TEXT_HTML ||
828
                                 partinfo->mime_type == MIME_MESSAGE_RFC822 ||
829
                                 partinfo->mime_type == MIME_IMAGE ||
830
                                 partinfo->mime_type == MIME_MULTIPART))
831
                        menu_set_sensitive(mimeview->popupfactory,
832
                                           "/Display as text", FALSE);
833
                else
834
                        menu_set_sensitive(mimeview->popupfactory,
835
                                           "/Display as text", TRUE);
836
                if (partinfo &&
837
                    partinfo->mime_type == MIME_APPLICATION_OCTET_STREAM)
838
                        menu_set_sensitive(mimeview->popupfactory,
839
                                           "/Open", FALSE);
840
                else
841
                        menu_set_sensitive(mimeview->popupfactory,
842
                                           "/Open", TRUE);
843
#if USE_GPGME
844
                menu_set_sensitive(mimeview->popupfactory,
845
                                   "/Check signature",
846
                                   mimeview_is_signed(mimeview));
847
#endif
848
849
                gtk_menu_popup(GTK_MENU(mimeview->popupmenu),
850
                               NULL, NULL, NULL, NULL,
851
                               event->button, event->time);
852
                return TRUE;
853
        }
854
855
        return FALSE;
856
}
857
858
void mimeview_pass_key_press_event(MimeView *mimeview, GdkEventKey *event)
859
{
860
        mimeview_key_pressed(mimeview->treeview, event, mimeview);
861
}
862
863
#define BREAK_ON_MODIFIER_KEY() \
864
        if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) != 0) break
865
866
#define KEY_PRESS_EVENT_STOP() \
867
        g_signal_stop_emission_by_name(G_OBJECT(treeview), "key_press_event");
868
869
static gint mimeview_key_pressed(GtkWidget *widget, GdkEventKey *event,
870
                                 MimeView *mimeview)
871
{
872
        SummaryView *summaryview = NULL;
873
        GtkTreeView *treeview = GTK_TREE_VIEW(widget);
874
        GtkTreeModel *model = GTK_TREE_MODEL(mimeview->store);
875
        GtkTreeIter iter;
876
        gboolean mod_pressed;
877
878
        if (!event) return FALSE;
879
        if (!mimeview->opened) return FALSE;
880
        if (!gtk_tree_model_get_iter(model, &iter, mimeview->opened))
881
                return FALSE;
882
883
        if (mimeview->messageview->mainwin)
884
                summaryview = mimeview->messageview->mainwin->summaryview;
885
        mod_pressed =
886
                ((event->state & (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
887
888
        switch (event->keyval) {
889
        case GDK_space:
890
                if (textview_scroll_page(mimeview->textview, mod_pressed))
891
                        return TRUE;
892
893
                if (gtkut_tree_model_next(model, &iter)) {
894
                        GtkTreePath *path;
895
896
                        path = gtk_tree_model_get_path(model, &iter);
897
                        gtk_tree_view_set_cursor(treeview, path, NULL, FALSE);
898
                        gtk_tree_path_free(path);
899
                        return TRUE;
900
                }
901
                if (summaryview)
902
                        summary_pass_key_press_event(summaryview, event);
903
                break;
904
        case GDK_BackSpace:
905
                textview_scroll_page(mimeview->textview, TRUE);
906
                return TRUE;
907
        case GDK_Return:
908
                textview_scroll_one_line(mimeview->textview, mod_pressed);
909
                return TRUE;
910
        case GDK_t:
911
                BREAK_ON_MODIFIER_KEY();
912
                KEY_PRESS_EVENT_STOP();
913
                mimeview_display_as_text(mimeview);
914
                return TRUE;
915
        case GDK_Escape:
916
                if (summaryview)
917
                        gtk_widget_grab_focus(summaryview->treeview);
918
                break;
919
        case GDK_Left:
920
        case GDK_Delete:
921
                if (summaryview)
922
                        summary_pass_key_press_event(summaryview, event);
923
                break;
924
        default:
925
                break;
926
        }
927
928
        return FALSE;
929
}
930
931
static void mimeview_drag_begin(GtkWidget *widget, GdkDragContext *drag_context,
932
                                MimeView *mimeview)
933
{
934
        gchar *filename;
935
        gchar *bname = NULL;
936
        MimeInfo *partinfo;
937
938
        if (!mimeview->opened) return;
939
        if (!mimeview->file) return;
940
941
        partinfo = mimeview_get_selected_part(mimeview);
942
        if (!partinfo) return;
943
944
        filename = partinfo->filename ? partinfo->filename : partinfo->name;
945
        if (filename) {
946
                const gchar *bname_;
947
948
                bname_ = g_basename(filename);
949
                bname = conv_filename_from_utf8(bname_);
950
                subst_for_filename(bname);
951
        }
952
        if (!bname || *bname == '\0')
953
                filename = procmime_get_tmp_file_name(partinfo);
954
        else
955
                filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
956
                                       bname, NULL);
957
958
        if (procmime_get_part(filename, mimeview->file, partinfo) < 0) {
959
                g_warning(_("Can't save the part of multipart message."));
960
        } else
961
                mimeview->drag_file = encode_uri(filename);
962
963
        g_free(filename);
964
965
        gtk_drag_set_icon_default(drag_context);
966
}
967
968
static void mimeview_drag_end(GtkWidget *widget, GdkDragContext *drag_context,
969
                              MimeView *mimeview)
970
{
971
        if (mimeview->drag_file) {
972
                g_free(mimeview->drag_file);
973
                mimeview->drag_file = NULL;
974
        }
975
}
976
977
static void mimeview_drag_data_get(GtkWidget            *widget,
978
                                   GdkDragContext   *drag_context,
979
                                   GtkSelectionData *selection_data,
980
                                   guint             info,
981
                                   guint             time,
982
                                   MimeView            *mimeview)
983
{
984
        if (!mimeview->drag_file) return;
985
986
        gtk_selection_data_set(selection_data, selection_data->target, 8,
987
                               (guchar *)mimeview->drag_file,
988
                               strlen(mimeview->drag_file));
989
}
990
991
static void mimeview_display_as_text(MimeView *mimeview)
992
{
993
        MimeInfo *partinfo;
994
995
        if (!mimeview->opened) return;
996
997
        partinfo = mimeview_get_selected_part(mimeview);
998
        g_return_if_fail(partinfo != NULL);
999
        mimeview_show_message_part(mimeview, partinfo);
1000
}
1001
1002
void mimeview_save_as(MimeView *mimeview)
1003
{
1004
        gchar *filename;
1005
        gchar *defname = NULL;
1006
        MimeInfo *partinfo;
1007
1008
        if (!mimeview->opened) return;
1009
        if (!mimeview->file) return;
1010
1011
        partinfo = mimeview_get_selected_part(mimeview);
1012
        g_return_if_fail(partinfo != NULL);
1013
1014
        if (partinfo->filename)
1015
                defname = partinfo->filename;
1016
        else if (partinfo->name) {
1017
                Xstrdup_a(defname, partinfo->name, return);
1018
                subst_for_filename(defname);
1019
        }
1020
1021
        filename = filesel_save_as(defname);
1022
        if (!filename) return;
1023
1024
        if (procmime_get_part(filename, mimeview->file, partinfo) < 0)
1025
                alertpanel_error
1026
                        (_("Can't save the part of multipart message."));
1027
1028
        g_free(filename);
1029
}
1030
1031
void mimeview_save_all(MimeView *mimeview)
1032
{
1033
        gchar *dir;
1034
1035
        dir = filesel_select_dir(NULL);
1036
        if (!dir) return;
1037
1038
        if (procmime_get_all_parts(dir, mimeview->file, mimeview->mimeinfo) < 0)
1039
                alertpanel_error(_("Can't save the attachments."));
1040
1041
        g_free(dir);
1042
}
1043
1044
static void mimeview_launch(MimeView *mimeview)
1045
{
1046
        MimeInfo *partinfo;
1047
        gchar *filename;
1048
1049
        if (!mimeview->opened) return;
1050
        if (!mimeview->file) return;
1051
1052
        partinfo = mimeview_get_selected_part(mimeview);
1053
        g_return_if_fail(partinfo != NULL);
1054
1055
        filename = procmime_get_tmp_file_name(partinfo);
1056
1057
        if (procmime_get_part(filename, mimeview->file, partinfo) < 0)
1058
                alertpanel_error
1059
                        (_("Can't save the part of multipart message."));
1060
        else
1061
                mimeview_view_file(filename, partinfo, NULL);
1062
1063
        g_free(filename);
1064
}
1065
1066
static void mimeview_open_with(MimeView *mimeview)
1067
{
1068
        MimeInfo *partinfo;
1069
        gchar *filename;
1070
        gchar *cmd;
1071
1072
        if (!mimeview->opened) return;
1073
        if (!mimeview->file) return;
1074
1075
        partinfo = mimeview_get_selected_part(mimeview);
1076
        g_return_if_fail(partinfo != NULL);
1077
1078
        filename = procmime_get_tmp_file_name(partinfo);
1079
1080
        if (procmime_get_part(filename, mimeview->file, partinfo) < 0) {
1081
                alertpanel_error
1082
                        (_("Can't save the part of multipart message."));
1083
                g_free(filename);
1084
                return;
1085
        }
1086
1087
        if (!prefs_common.mime_open_cmd_history)
1088
                prefs_common.mime_open_cmd_history =
1089
                        add_history(NULL, prefs_common.mime_open_cmd);
1090
1091
        cmd = input_dialog_combo
1092
                (_("Open with"),
1093
                 _("Enter the command line to open file:\n"
1094
                   "(`%s' will be replaced with file name)"),
1095
                 prefs_common.mime_open_cmd,
1096
                 prefs_common.mime_open_cmd_history,
1097
                 TRUE);
1098
        if (cmd) {
1099
                mimeview_view_file(filename, partinfo, cmd);
1100
                g_free(prefs_common.mime_open_cmd);
1101
                prefs_common.mime_open_cmd = cmd;
1102
                prefs_common.mime_open_cmd_history =
1103
                        add_history(prefs_common.mime_open_cmd_history, cmd);
1104
        }
1105
1106
        g_free(filename);
1107
}
1108
1109
static void mimeview_view_file(const gchar *filename, MimeInfo *partinfo,
1110
                               const gchar *cmdline)
1111
{
1112
        static gchar *default_image_cmdline = "display '%s'";
1113
        static gchar *default_audio_cmdline = "play '%s'";
1114
        static gchar *default_html_cmdline = DEFAULT_BROWSER_CMD;
1115
        static gchar *mime_cmdline = "metamail -d -b -x -c %s '%s'";
1116
        gchar buf[1024];
1117
        gchar m_buf[1024];
1118
        const gchar *cmd;
1119
        const gchar *def_cmd;
1120
        const gchar *p;
1121
1122
#ifdef G_OS_WIN32
1123
        if (!cmdline) {
1124
                DWORD dwtype;
1125
                AlertValue avalue;
1126
1127
                if (str_has_suffix_case(filename, ".exe") ||
1128
                    str_has_suffix_case(filename, ".com") ||
1129
                    str_has_suffix_case(filename, ".scr") ||
1130
                    str_has_suffix_case(filename, ".pif") ||
1131
                    str_has_suffix_case(filename, ".bat") ||
1132
                    str_has_suffix_case(filename, ".vbs") ||
1133
                    str_has_suffix_case(filename, ".js")  ||
1134
                    GetBinaryType(filename, &dwtype)) {
1135
                        avalue = alertpanel_full
1136
                                (_("Opening executable file"),
1137
                                 _("This is an executable file. Do you really want to launch it?"),
1138
                                 ALERT_WARNING, G_ALERTALTERNATE, FALSE,
1139
                                 GTK_STOCK_YES, GTK_STOCK_NO, NULL);
1140
                        if (avalue != G_ALERTDEFAULT)
1141
                                return;
1142
                }
1143
                execute_open_file(filename, partinfo->content_type);
1144
                return;
1145
        }
1146
#endif
1147
        if (cmdline) {
1148
                cmd = cmdline;
1149
                def_cmd = NULL;
1150
        } else if (MIME_APPLICATION_OCTET_STREAM == partinfo->mime_type) {
1151
                return;
1152
        } else if (MIME_IMAGE == partinfo->mime_type) {
1153
                cmd = prefs_common.mime_image_viewer;
1154
                def_cmd = default_image_cmdline;
1155
        } else if (MIME_AUDIO == partinfo->mime_type) {
1156
                cmd = prefs_common.mime_audio_player;
1157
                def_cmd = default_audio_cmdline;
1158
        } else if (MIME_TEXT_HTML == partinfo->mime_type) {
1159
                cmd = prefs_common.uri_cmd;
1160
                def_cmd = default_html_cmdline;
1161
        } else {
1162
                g_snprintf(m_buf, sizeof(m_buf), mime_cmdline,
1163
                           partinfo->content_type, "%s");
1164
                cmd = m_buf;
1165
                def_cmd = NULL;
1166
        }
1167
1168
        if (cmd && (p = strchr(cmd, '%')) && *(p + 1) == 's' &&
1169
            !strchr(p + 2, '%'))
1170
                g_snprintf(buf, sizeof(buf), cmd, filename);
1171
        else {
1172
                if (cmd)
1173
                        g_warning(_("MIME viewer command line is invalid: `%s'"), cmd);
1174
                if (def_cmd)
1175
                        g_snprintf(buf, sizeof(buf), def_cmd, filename);
1176
                else
1177
                        return;
1178
        }
1179
1180
        execute_command_line(buf, TRUE);
1181
}
1182
1183
#if USE_GPGME
1184
static gboolean update_node_name_func(GtkTreeModel *model, GtkTreePath *path,
1185
                                      GtkTreeIter *iter, gpointer data)
1186
{
1187
        MimeInfo *partinfo;
1188
        gchar *part_name;
1189
1190
        gtk_tree_model_get(model, iter, COL_MIME_INFO, &partinfo, -1);
1191
        g_return_val_if_fail(partinfo != NULL, FALSE);
1192
1193
        part_name = get_part_name(partinfo);
1194
        gtk_tree_store_set(GTK_TREE_STORE(model), iter, COL_NAME, part_name,
1195
                           -1);
1196
1197
        return FALSE;
1198
}
1199
1200
static void mimeview_update_names(MimeView *mimeview)
1201
{
1202
        gtk_tree_model_foreach(GTK_TREE_MODEL(mimeview->store),
1203
                               update_node_name_func, NULL);
1204
}
1205
1206
static void mimeview_update_signature_info(MimeView *mimeview)
1207
{
1208
        MimeInfo *partinfo;
1209
1210
        if (!mimeview) return;
1211
        if (!mimeview->opened) return;
1212
1213
        partinfo = mimeview_get_selected_part(mimeview);
1214
        if (!partinfo) return;
1215
1216
        if (rfc2015_is_signature_part(partinfo)) {
1217
                mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
1218
                mimeview_show_signature_part(mimeview, partinfo);
1219
        }
1220
}
1221
1222
static void mimeview_check_signature(MimeView *mimeview)
1223
{
1224
        MimeInfo *mimeinfo;
1225
        FILE *fp;
1226
1227
        g_return_if_fail (mimeview_is_signed(mimeview));
1228
1229
        mimeinfo = mimeview_get_selected_part(mimeview);
1230
        g_return_if_fail(mimeinfo != NULL);
1231
        g_return_if_fail(mimeview->file != NULL);
1232
1233
        while (mimeinfo->parent)
1234
                mimeinfo = mimeinfo->parent;
1235
1236
        if ((fp = g_fopen(mimeview->file, "rb")) == NULL) {
1237
                FILE_OP_ERROR(mimeview->file, "fopen");
1238
                return;
1239
        }
1240
1241
        rfc2015_check_signature(mimeinfo, fp);
1242
        fclose(fp);
1243
1244
        mimeview_update_names(mimeview);
1245
        mimeview_update_signature_info(mimeview);
1246
1247
        textview_show_message(mimeview->messageview->textview, mimeinfo,
1248
                              mimeview->file);
1249
}
1250
#endif /* USE_GPGME */